作者:祝愿我上钱一班
版本:1.1
文章目录
简介功能特点使用方法基础使用步骤常用启动模式进阶设置选项
代码详解核心概念解析主要函数分析1. `start()` - 启动函数2. `processPost()` - 处理单条动态3. `performLike()` - 执行点赞操作4. `performComment()` - 执行评论操作5. `isAlreadyLiked()` - 检查是否已点赞6. `hasAlreadyCommented()` - 检查是否已评论7. `findProcessablePosts()` - 查找可处理的动态
工作流程详解具体实现详解1. 脚本结构2. 配置和状态管理3. 动态查找实现4. 点赞实现5. 评论实现6. 自动滚动加载更多7. DOM操作工具函数8. 评论内容生成9. 错误处理
常见问题解答使用技巧完整代码:安全提示
简介
这是一个专为QQ空间设计的自动化点赞和评论工具,可以帮助你自动浏览好友动态并进行互动。无需下载安装任何软件,只需要在浏览器中打开QQ空间并运行这段代码,就能实现自动化操作。对于想要提高QQ空间活跃度但又没有太多时间手动操作的用户来说,这是一个非常实用的小工具。
💡 零基础提示:这个工具实际上是一段JavaScript代码,它可以在浏览器中自动执行一些你平时需要手动完成的操作,比如点赞和评论。不需要担心代码看起来复杂,按照本指南操作即可轻松使用。
功能特点
智能判断:自动识别哪些动态已经点过赞或已经评论过,避免重复操作灵活配置:支持仅点赞模式、点赞+评论模式,可根据需求灵活设置随机评论:从评论库中随机选择评论内容,使互动看起来更自然自动翻页:处理完当前页面的内容后,会自动滚动加载更多动态操作可控:可以设置最大操作数量,随时可以停止运行详细反馈:在浏览器控制台清晰显示每一步的操作结果
使用方法
基础使用步骤
打开QQ空间:确保你已经登录QQ空间打开浏览器控制台:
在Windows上按下键盘上的 F12 键或者右键点击页面空白处,选择"检查"或"审查元素" 粘贴代码:将提供的脚本代码完整复制,粘贴到控制台中执行代码:按回车键执行启动功能:在控制台中输入启动命令(见下方常用启动模式)
💡 零基础提示:控制台是浏览器中的一个工具,允许开发者执行代码。不要担心它看起来很技术化,你只需要把它当作一个输入命令的地方即可。
常用启动模式
根据你的需求,可以选择不同的启动命令:
// 1. 标准模式:先点赞后评论
QzAllInOne.start()
// 2. 仅点赞模式:只点赞不评论
QzAllInOne.start({doComment: false})
// 3. 智能模式:跳过已点赞的内容,只处理新内容
QzAllInOne.start({skipLiked: true})
// 4. 超级节省模式:跳过已点赞内容且不评论
QzAllInOne.start({skipLiked: true, doComment: false})
💡 零基础提示:上面的命令中,花括号 {} 里面的内容是设置项,可以控制脚本的行为。例如 doComment: false 表示不进行评论,只点赞。
进阶设置选项
如果你想更精细地控制脚本的行为,可以使用以下高级设置:
QzAllInOne.start({
// 最多处理多少条动态,设为0表示不限制
maxActions: 20,
// 处理每条动态的间隔时间(毫秒),1000毫秒=1秒
actionInterval: 8000,
// 是否跳过已点赞的内容
skipLiked: true,
// 是否进行评论
doComment: true,
// 点赞失败时是否仍然评论
commentWithoutLike: false,
// 自定义评论内容
commentTexts: [
"不错的分享,点赞支持!",
"每天打卡必赞~",
"你的动态总是这么有趣!"
]
})
其他实用命令:
// 停止运行
QzAllInOne.stop()
// 添加一条新评论到评论库
QzAllInOne.addComment("这条动态真棒!")
// 查看当前运行状态和统计信息
QzAllInOne.getStats()
// 设置为仅点赞模式
QzAllInOne.setOnlyLike(true)
// 设置跳过已点赞内容
QzAllInOne.setSkipLiked(true)
代码详解
核心概念解析
对于零基础的朋友,先了解几个基本概念:
函数:就像是一个能完成特定任务的机器人。例如,start()函数的任务是启动整个脚本。
参数:给函数的指令或材料。例如,当我们写QzAllInOne.start({doComment: false})时,{doComment: false}就是参数,告诉函数"不要评论"。
变量:用来存储信息的容器。例如,config变量存储了所有配置信息。
条件判断:让程序能够作出决策。例如,if(isAlreadyLiked)表示"如果已经点过赞了,那么…"
对象:包含多个相关数据的集合。例如stats对象包含了点赞数、评论数等统计信息。
数组:多个数据的有序集合。例如commentTexts数组包含了多条评论内容。
主要函数分析
脚本中的主要函数及其作用:
1. start() - 启动函数
这是脚本的入口,负责初始化和开始整个自动化过程。
function start(options = {}) {
// 合并配置,检查环境,重置统计,启动处理流程
}
代码解释:
options = {}表示如果不提供参数,就使用空对象作为默认值函数内部会检查当前是否在QQ空间环境初始化各种统计数据设置必要的监听器开始处理第一批动态
2. processPost() - 处理单条动态
负责处理单条动态的点赞和评论操作。
async function processPost(post, postId) {
// 确保动态可见,执行点赞,如果点赞成功且需要评论则执行评论
}
代码解释:
async表示这是一个异步函数,可以等待耗时操作完成post是动态的HTML元素,postId是动态的唯一标识函数内部首先滚动到该动态位置然后执行点赞操作如果点赞成功且配置了需要评论,则执行评论操作
3. performLike() - 执行点赞操作
负责查找点赞按钮并执行点赞。
async function performLike(post, postId) {
// 查找点赞按钮,检查是否已点赞,执行点赞并验证结果
}
代码解释:
首先检查是否已经点过赞如果已点赞且设置了跳过已点赞内容,则直接返回成功否则点击点赞按钮并等待一段时间检查点赞是否成功,并返回结果
4. performComment() - 执行评论操作
负责添加随机评论。
async function performComment(post, postId) {
// 检查是否已评论,打开评论框,填写内容,发送评论
}
代码解释:
首先检查是否已经评论过如果已评论且设置了跳过已评论内容,则直接返回成功否则点击评论按钮打开评论框从评论库中随机选择一条评论填入点击发送按钮或按回车键发送验证评论是否成功发送
5. isAlreadyLiked() - 检查是否已点赞
function isAlreadyLiked(likeButton) {
// 通过多种方法检查点赞按钮状态
}
代码解释:
检查按钮的CSS类是否包含表示已点赞的标记检查按钮颜色是否变为点赞后的颜色检查按钮文本是否包含"已赞"或"取消"检查点赞计数是否包含"我"
6. hasAlreadyCommented() - 检查是否已评论
function hasAlreadyCommented(post) {
// 检查评论列表中是否有当前用户的评论
}
代码解释:
查找评论列表中是否包含当前登录用户的昵称检查评论按钮文本是否有特殊提示检查是否有删除自己评论的选项
7. findProcessablePosts() - 查找可处理的动态
function findProcessablePosts() {
// 使用各种选择器查找页面上的动态
}
代码解释:
使用多种CSS选择器查找匹配QQ空间动态的元素过滤掉广告和特殊内容确保每条动态都有点赞和评论按钮
工作流程详解
整个脚本的工作流程如下:
初始化阶段:
加载配置检查环境重置统计数据设置监听器 查找动态阶段:
使用多种选择器查找页面上的动态过滤掉广告或不支持互动的内容 处理动态阶段:
对每条动态,先判断是否已处理过执行点赞操作(先判断是否已点赞)如需评论,执行评论操作(先判断是否已评论)记录处理结果 自动加载更多阶段:
处理完当前页面后自动滚动等待新内容加载继续处理新动态 完成或停止阶段:
达到最大处理数量或滚动到底部时完成或用户手动停止时结束输出统计结果
具体实现详解
下面我们深入了解脚本的具体实现细节,看看它是如何一步步完成自动点赞和评论的。
1. 脚本结构
整个脚本采用了一种叫做"立即执行函数表达式"(IIFE)的结构,这样可以避免变量污染全局空间:
const QzAllInOne = (function() {
// 这里是所有的代码
// ...
// 最后返回公开API
return {
start, stop, setCommentTexts, /* 其他公开方法 */
};
})();
💡 零基础提示:这种结构就像创建了一个封闭的空间,脚本内部的变量不会影响到网页的其他部分,避免冲突。
2. 配置和状态管理
脚本使用两个主要对象来管理配置和状态:
// 默认配置
const defaultConfig = {
maxActions: 20, // 最大操作数量
actionInterval: 8000, // 操作间隔
doComment: true, // 是否评论
// 其他配置...
};
// 运行时状态
let stats = {
started: false, // 是否已启动
processed: 0, // 已处理数量
liked: 0, // 已点赞数量
commented: 0, // 已评论数量
// 其他状态...
};
启动脚本时,用户的配置会与默认配置合并:
config = {...defaultConfig, ...options};
💡 零基础提示:{...a, ...b}是一种合并两个对象的简洁写法,如果有相同的属性,后面对象(b)的值会覆盖前面对象(a)的值。
3. 动态查找实现
脚本如何找到页面上的动态?这是通过CSS选择器实现的:
function findProcessablePosts() {
const selectors = [
'li.f-single', // 标准动态列表项
'div.f-single', // 替代形式
'li[id^="fct_"]', // 基于ID前缀
// 其他选择器...
];
// 使用这些选择器查找元素
const posts = [];
selectors.forEach(selector => {
const found = document.querySelectorAll(selector);
// 添加到结果数组...
});
// 过滤掉不可处理的内容
return posts.filter(post => {
// 过滤逻辑...
});
}
💡 零基础提示:CSS选择器是一种用来查找网页元素的语法。比如li.f-single表示"查找所有类名为f-single的li元素"。
4. 点赞实现
点赞功能的核心是找到点赞按钮并模拟点击:
async function performLike(post, postId) {
// 1. 查找点赞按钮
const likeButton = findLikeButton(post);
// 2. 检查是否已点赞
if (isAlreadyLiked(likeButton)) {
return true; // 已点赞,跳过
}
// 3. 执行点击
clickElement(likeButton);
// 4. 等待点赞反应
await sleep(1000);
// 5. 验证点赞是否成功
return verifyLikeSuccess(post, likeButton);
}
查找点赞按钮的实现:
function findLikeButton(post) {
// 1. 先查找特定图标
const iconButton = post.querySelector('i.fui-icon.icon-op-praise');
if (iconButton) {
return iconButton.parentElement;
}
// 2. 尝试其他选择器
const selectors = [
'a[data-cmd="qz_like"]',
'.qz_like_btn',
// 其他可能的选择器...
];
// 3. 尝试通过文本内容查找
// ...
}
判断是否已点赞的实现:
function isAlreadyLiked(likeButton) {
// 1. 通过类名判断
if (likeButton.classList.contains('item-liked')) {
return true;
}
// 2. 通过颜色判断
const style = getComputedStyle(likeButton);
if (style.color === 'rgb(251, 114, 153)') { // 粉红色通常表示已点赞
return true;
}
// 3. 通过文本内容判断
if (likeButton.textContent.includes('已赞')) {
return true;
}
// 其他判断方法...
}
5. 评论实现
评论功能更复杂,包含多个步骤:
async function performComment(post, postId) {
// 1. 检查是否已评论过
if (hasAlreadyCommented(post)) {
return true; // 已评论,跳过
}
// 2. 点击评论按钮打开评论框
const commentButton = findCommentButton(post);
clickElement(commentButton);
await sleep(1500);
// 3. 查找评论输入框
const commentInput = findCommentInput(post);
// 4. 生成随机评论内容
const commentText = generateComment();
// 5. 填写评论内容
await fillCommentInput(commentInput, commentText);
// 6. 发送评论
const sendButton = findSendButton(post);
if (sendButton) {
clickElement(sendButton);
} else {
sendCommentByEnter(commentInput);
}
// 7. 验证评论是否成功
await sleep(1500);
return verifyCommentSuccess(post, commentText);
}
填写评论内容的实现:
async function fillCommentInput(input, text) {
// 1. 判断输入框类型
const isContentEditable = input.getAttribute('contenteditable') === 'true';
if (isContentEditable) {
// 2a. 处理可编辑div元素
input.focus();
input.innerHTML = '';
document.execCommand('insertText', false, text);
} else {
// 2b. 处理textarea或input元素
input.value = text;
triggerEvent(input, 'input');
}
}
6. 自动滚动加载更多
处理完当前页面的动态后,脚本会自动滚动加载更多内容:
async function scrollDown() {
// 1. 记录当前页面高度
const previousHeight = document.body.scrollHeight;
// 2. 滚动页面
window.scrollBy(0, config.scrollDistance);
// 3. 等待新内容加载
await sleep(config.scrollInterval);
// 4. 检查页面高度是否变化
const newHeight = document.body.scrollHeight;
return newHeight > previousHeight; // 如果高度增加,说明加载了新内容
}
7. DOM操作工具函数
脚本包含多个用于DOM操作的工具函数:
// 模拟点击元素
function clickElement(element) {
if (!element) return;
try {
// 尝试使用标准点击方法
element.click();
} catch (err) {
// 降级:使用事件模拟点击
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
element.dispatchEvent(event);
}
}
// 检查元素是否可见
function isVisible(element) {
if (!element) return false;
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
element.offsetWidth > 0;
}
// 滚动到元素可见
function scrollToElement(element) {
// 实现...
}
8. 评论内容生成
脚本从评论库中随机选择一条评论:
function generateComment() {
const comments = config.commentTexts;
if (comments.length === 0) {
return "不错,支持一下!"; // 默认评论
}
const randomIndex = Math.floor(Math.random() * comments.length);
return comments[randomIndex];
}
9. 错误处理
脚本包含错误处理逻辑,确保单个动态的错误不会影响整体运行:
try {
// 尝试处理动态
await processPost(post, postId);
} catch (err) {
// 记录错误并继续处理下一条
error(`处理帖子出错: ${err.message}`);
stats.failed++;
}
常见问题解答
问:脚本运行时遇到了错误怎么办?
答:最常见的解决方法是刷新页面,重新执行脚本。如果持续出错,可能是QQ空间页面结构有变化,需要更新脚本。
问:为什么有些动态没有被点赞或评论?
答:可能的原因有:
该动态已经点赞/评论过(且设置了跳过已处理内容)该动态是广告或特殊内容,不支持互动点赞/评论操作失败(网络问题或页面交互问题)
问:如何停止脚本运行?
答:在控制台输入 QzAllInOne.stop() 即可停止。也可以刷新页面或关闭浏览器标签。
问:脚本会泄露我的QQ账号密码吗?
答:不会。脚本只在已登录的QQ空间页面上运行,不涉及账号密码处理,也不会向外部发送任何数据。
使用技巧
自定义评论库:添加个性化的评论内容,使互动更加自然。
QzAllInOne.setCommentTexts([
"这个我喜欢👍",
"不错的分享!",
"最近怎么样?"
])
控制处理速度:如果担心操作过于频繁,可以增加操作间隔时间。
QzAllInOne.setInterval(10000) // 设置为10秒间隔
分批次处理:设置较小的最大操作数,完成后再启动下一批。
QzAllInOne.start({maxActions: 10}) // 只处理10条
先点赞后评论:如果想先对所有内容点赞,再进行评论,可以:
// 第一步:只点赞
QzAllInOne.start({doComment: false})
// 待完成后
// 第二步:只评论已点赞内容
QzAllInOne.start({commentWithoutLike: true})
完整代码:
// ================ QQ空间自动点赞+评论联动脚本 ================
// 作者: 祝愿我上钱一班
// 版本: 1.0
// 功能说明: 在QQ空间自动给动态先点赞后评论,按顺序处理每条动态
// 使用方法: 在QQ空间页面打开控制台(F12),粘贴此脚本,输入QzAllInOne.start()即可启动
// ==================================================
/**
* QQ空间全能助手 - 集成点赞和评论功能
*/
const QzAllInOne = (function() {
// =============== 配置区域 ===============
const defaultConfig = {
// 基本设置
maxActions: 20, // 最大操作次数(0表示不限制)
actionInterval: 8000, // 每个帖子的操作间隔(毫秒)
autoScroll: true, // 是否自动滚动加载更多内容
scrollInterval: 2000, // 滚动间隔(毫秒)
scrollDistance: 800, // 每次滚动距离(像素)
// 点赞设置
likeInterval: 1000, // 点赞操作后等待时间(毫秒)
skipLiked: true, // 跳过已点赞的帖子
// 评论设置
doComment: true, // 是否进行评论操作
commentWithoutLike: false, // 点赞失败时是否仍然评论
commentDelay: 2000, // 点赞后等待多久开始评论(毫秒)
commentTexts: [
"很棒的分享,谢谢!👍",
"每天来看看,支持一下~",
"最近怎么样?👋",
"不错的内容,学习了",
"路过支持一下~",
"这个内容很有意思!",
"感谢分享!",
"很高兴看到你的更新😊",
"继续加油哦!💪",
"这个我很喜欢,已收藏"
],
skipCommented: true, // 跳过已评论过的帖子
// 操作设置
maxRetries: 3, // 失败时最大重试次数
retryDelay: 2000, // 重试等待时间(毫秒)
debugMode: true // 调试模式(显示详细日志)
};
// 内部状态
let config = {...defaultConfig};
let stats = {
started: false,
processed: 0,
skipped: 0,
liked: 0,
commented: 0,
failed: 0,
retries: 0
};
let processedPosts = new Set(); // 已处理帖子ID集合
let observer = null; // 动态监听器
// =============== 核心函数 ===============
/**
* 启动自动点赞+评论
* @param {Object} options - 可选配置参数
*/
function start(options = {}) {
if (stats.started) {
log("脚本已在运行中,请勿重复启动");
return;
}
// 合并配置
config = {...defaultConfig, ...options};
// 检查环境
if (!isQzoneEnvironment()) {
error("请在QQ空间页面运行此脚本!");
return;
}
// 重置统计数据
stats = {
started: true,
processed: 0,
skipped: 0,
liked: 0,
commented: 0,
failed: 0,
retries: 0
};
processedPosts.clear();
// 显示启动信息
log("===== QQ空间自动点赞+评论助手启动 =====");
log(`最大操作数: ${config.maxActions || "不限"}, 操作间隔: ${config.actionInterval}ms`);
log(`已加载 ${config.commentTexts.length} 条评论模板`);
if (config.doComment === false) {
log("已设置仅执行点赞操作,不进行评论");
} else {
log(`先点赞${config.commentWithoutLike?'(可选)':'(必需)'},${config.doComment !== false ? '后评论' : '不评论'}`);
}
// 添加必要的DOM扩展函数
enhanceDom();
// 设置动态监听(DOM变化时尝试处理)
setupObserver();
// 开始处理流程
processNextBatch();
}
/**
* 处理下一批帖子
*/
async function processNextBatch() {
if (!stats.started) return;
// 检查是否达到最大操作数
if (config.maxActions > 0 && stats.processed >= config.maxActions) {
finish();
return;
}
try {
// 查找可处理的帖子
const posts = findProcessablePosts();
if (posts.length === 0) {
if (config.autoScroll) {
// 没有找到帖子时,滚动页面加载更多
log("没有找到可处理的帖子,尝试滚动加载更多...");
const hasMore = await scrollDown();
if (hasMore) {
// 等待新内容加载完成
setTimeout(processNextBatch, config.scrollInterval);
} else {
log("已滚动到页面底部,没有更多内容了");
finish();
}
} else {
log("没有找到可处理的帖子");
finish();
}
return;
}
// 处理找到的帖子
log(`找到 ${posts.length} 个可处理的帖子`);
// 对每个帖子进行处理
for (const post of posts) {
// 再次检查是否达到最大操作数或已停止
if (!stats.started || (config.maxActions > 0 && stats.processed >= config.maxActions)) {
finish();
return;
}
// 获取帖子ID
const postId = getPostId(post);
// 检查是否已处理过
if (processedPosts.has(postId)) {
stats.skipped++;
debug(`跳过已处理的帖子: ${postId}`);
continue;
}
// 处理这条帖子(先点赞后评论)
await processPost(post, postId);
// 等待指定的间隔时间
await sleep(config.actionInterval);
}
// 处理完当前批次后,继续查找下一批
if (config.autoScroll) {
await scrollDown();
}
// 安排下一批处理
setTimeout(processNextBatch, config.scrollInterval);
} catch (err) {
error("处理帖子时出错:", err);
// 错误后仍然继续尝试
setTimeout(processNextBatch, config.retryDelay);
}
}
/**
* 处理单个帖子(先点赞后评论)
* @param {Element} post - 帖子元素
* @param {string} postId - 帖子ID
*/
async function processPost(post, postId) {
debug(`开始处理帖子: ${postId}`);
log(`开始处理帖子 #${stats.processed + 1}: ${postId}`);
try {
// 确保帖子可见
scrollToElement(post);
await sleep(300);
// 步骤1: 点赞
const likeSuccess = await performLike(post, postId);
// 等待点赞操作完成
await sleep(config.likeInterval);
// 步骤2: 评论(只有在设置了需要评论时进行)
let commentSuccess = false;
if (config.doComment !== false) {
// 如果点赞成功或者设置了即使点赞失败也评论,则评论
if (likeSuccess || config.commentWithoutLike) {
await sleep(config.commentDelay);
commentSuccess = await performComment(post, postId);
} else {
log(`跳过评论: 点赞未成功`);
}
} else {
debug(`已设置不进行评论操作`);
}
// 记录处理成功
processedPosts.add(postId);
stats.processed++;
// 返回详细的处理结果
const result = {
postId: postId,
likeSuccess: likeSuccess,
commentSuccess: commentSuccess
};
log(`✓ 处理完成 [${stats.processed}/${config.maxActions || "∞"}]: ${postId} 点赞:${likeSuccess?'成功':'失败'} 评论:${commentSuccess?'成功':config.doComment===false?'已禁用':'失败'}`);
return result;
} catch (err) {
error(`处理帖子出错: ${err.message}`);
stats.failed++;
return {
postId: postId,
likeSuccess: false,
commentSuccess: false,
error: err.message
};
}
}
/**
* 执行点赞操作
* @param {Element} post - 帖子元素
* @param {string} postId - 帖子ID
*/
async function performLike(post, postId) {
debug(`尝试点赞帖子: ${postId}`);
try {
// 查找点赞按钮
const likeButton = findLikeButton(post);
if (!likeButton) {
debug("未找到点赞按钮");
return false;
}
// 检查是否已点赞 - 更严格的判断
if (isAlreadyLiked(likeButton)) {
debug("该帖子已被点赞,跳过点赞操作");
log(`✓ 帖子 ${postId} 已经点过赞了,无需重复点赞`);
stats.skipped++;
return true; // 已点赞也算成功
}
debug("找到点赞按钮,点击执行点赞");
log(`正在点赞帖子: ${postId}`);
clickElement(likeButton);
// 等待点赞状态更新
await sleep(1000);
// 验证点赞是否成功 - 二次确认
const isSuccess = verifyLikeSuccess(post, likeButton);
if (isSuccess) {
stats.liked++;
debug(`点赞成功: ${postId}`);
log(`✓ 点赞成功: ${postId}`);
return true;
} else {
debug(`点赞可能失败: ${postId},再次尝试验证`);
// 额外等待一点时间再次验证
await sleep(500);
if (isAlreadyLiked(likeButton)) {
stats.liked++;
debug(`延迟验证点赞成功: ${postId}`);
log(`✓ 点赞成功: ${postId}`);
return true;
}
debug(`点赞失败: ${postId}`);
log(`✗ 点赞失败: ${postId}`);
return false;
}
} catch (err) {
debug(`点赞操作出错: ${err.message}`);
log(`✗ 点赞出错: ${postId}`);
return false;
}
}
/**
* 执行评论操作
* @param {Element} post - 帖子元素
* @param {string} postId - 帖子ID
*/
async function performComment(post, postId) {
debug(`尝试评论帖子: ${postId}`);
try {
// 首先检查该帖子是否已评论过
if (hasAlreadyCommented(post)) {
debug("该帖子已评论过,跳过评论操作");
log(`✓ 帖子 ${postId} 已经评论过了,无需重复评论`);
stats.skipped++;
return true; // 已评论也算成功
}
// 步骤1: 点击评论按钮打开评论框
const commentButton = findCommentButton(post);
if (!commentButton) {
debug("未找到评论按钮");
log(`✗ 评论失败: 未找到评论按钮`);
return false;
}
debug("找到评论按钮,点击打开评论框");
log(`正在准备评论帖子: ${postId}`);
clickElement(commentButton);
await sleep(1500); // 增加等待时间,确保评论框完全加载
// 步骤2: 查找并填写评论框
const commentInput = findCommentInput(post);
if (!commentInput) {
debug("未找到评论输入框");
log(`✗ 评论失败: 未找到评论输入框`);
return false;
}
// 生成随机评论内容
const commentText = generateComment();
debug(`找到评论框,填写内容: ${commentText}`);
// 填写评论内容
await fillCommentInput(commentInput, commentText);
await sleep(500); // 短暂等待,确保内容填写完成
// 步骤3: 查找并点击发送按钮
const sendButton = findSendButton(post);
if (!sendButton) {
debug("未找到发送按钮");
log(`✗ 评论失败: 未找到发送按钮`);
// 尝试按回车键发送
if (sendCommentByEnter(commentInput)) {
debug("通过回车键发送评论");
log(`尝试通过回车键发送评论`);
} else {
return false;
}
} else {
debug("找到发送按钮,点击发送评论");
log(`正在发送评论...`);
clickElement(sendButton);
}
// 等待评论提交
await sleep(1500);
// 验证评论是否成功
if (verifyCommentSuccess(post, commentText)) {
stats.commented++;
debug(`评论成功: ${postId}`);
log(`✓ 评论成功: ${postId}`);
return true;
} else {
debug(`评论可能失败,再次检查`);
// 再次等待一段时间检查
await sleep(1000);
if (verifyCommentSuccess(post, commentText)) {
stats.commented++;
debug(`延迟验证评论成功: ${postId}`);
log(`✓ 评论成功: ${postId}`);
return true;
}
log(`✗ 评论可能失败: ${postId}`);
return false;
}
} catch (err) {
debug(`评论操作出错: ${err.message}`);
log(`✗ 评论出错: ${postId}`);
return false;
}
}
/**
* 检查帖子是否已被当前用户评论过
* @param {Element} post - 帖子元素
* @returns {boolean} 是否已评论
*/
function hasAlreadyCommented(post) {
try {
// 1. 检查评论区是否有显示当前用户的头像或昵称
const commentList = post.querySelector('.comments-list, .comment-list');
if (commentList) {
// 尝试找到评论列表中包含当前用户信息的评论
const myComments = commentList.querySelectorAll('.comments-item, .comment-item');
// 获取当前登录的QQ昵称元素
const myNickElement = document.querySelector('.side_user a.nickname, .user-info .user-name');
const myNick = myNickElement ? myNickElement.textContent.trim() : '';
// 检查评论列表中是否有自己的评论
for (const comment of myComments) {
const commenterName = comment.querySelector('.nickname, .user-name');
if (commenterName && commenterName.textContent.trim() === myNick) {
return true;
}
// 检查评论者的QQ号或其他标识
const commenterLink = comment.querySelector('a[href*="profile"]');
if (commenterLink && commenterLink.href.includes('myself=true')) {
return true;
}
}
}
// 2. 检查评论按钮文本是否有特殊提示(如"追评")
const commentButton = findCommentButton(post);
if (commentButton && commentButton.textContent.includes('追评')) {
return true;
}
// 3. 检查是否有管理自己评论的选项
const deleteCommentLinks = post.querySelectorAll('a[data-cmd="delComment"], .del-comment');
if (deleteCommentLinks.length > 0) {
return true;
}
return false;
} catch (err) {
debug(`检查评论状态出错: ${err.message}`);
return false; // 出错时假设未评论过
}
}
/**
* 验证评论是否发送成功
* @param {Element} post - 帖子元素
* @param {string} commentText - 发送的评论内容
* @returns {boolean} 评论是否成功发送
*/
function verifyCommentSuccess(post, commentText) {
try {
// 1. 检查评论列表是否更新
const commentList = post.querySelector('.comments-list, .comment-list');
if (commentList) {
const latestComment = commentList.querySelector('.comments-item:first-child, .comment-item:first-child');
if (latestComment) {
const commentContent = latestComment.querySelector('.comments-content, .comment-content');
if (commentContent && commentContent.textContent.includes(commentText)) {
return true;
}
}
}
// 2. 检查是否有评论成功的提示
const successTip = document.querySelector('.qz-tip, .success-tip');
if (successTip && isVisible(successTip)) {
return true;
}
// 3. 检查评论框是否已清空或消失
const commentInput = findCommentInput(post);
if (commentInput) {
// 如果输入框被清空,可能表示提交成功
if (commentInput.textContent === '' || commentInput.value === '') {
return true;
}
} else {
// 如果输入框消失,也可能表示提交成功
const commentArea = post.querySelector('.comment-box, .comments-box');
if (commentArea && !commentArea.querySelector('textarea, [contenteditable="true"]')) {
return true;
}
}
// 4. 检查是否已经能找到刚才发送的评论
return hasAlreadyCommented(post);
} catch (err) {
debug(`验证评论成功状态出错: ${err.message}`);
return false;
}
}
/**
* 完成任务
*/
function finish() {
if (!stats.started) return;
stats.started = false;
if (observer) {
observer.disconnect();
observer = null;
}
log("===== 任务完成 =====");
log(`处理帖子: ${stats.processed} 条`);
log(`点赞成功: ${stats.liked} 条`);
log(`评论成功: ${stats.commented} 条`);
log(`跳过帖子: ${stats.skipped} 条`);
log(`失败: ${stats.failed} 条`);
// 返回统计结果
return {
processed: stats.processed,
liked: stats.liked,
commented: stats.commented,
skipped: stats.skipped,
failed: stats.failed
};
}
/**
* 停止任务
*/
function stop() {
if (!stats.started) {
log("脚本尚未启动");
return;
}
log("正在停止任务...");
finish();
}
// =============== 辅助函数 ===============
/**
* 查找可处理的帖子
* @returns {Array} 帖子元素数组
*/
function findProcessablePosts() {
// 基于QQ空间HTML结构,查找帖子容器
const selectors = [
'li.f-single', // 标准动态列表项
'div.f-single', // 替代形式
'li[id^="fct_"]', // 基于ID前缀
'.feed_list > li', // 经典版动态列表
'.feeds_item, .feeds-item', // 其他可能形式
'.feed-item' // 其他可能形式
];
// 使用选择器查找元素
const posts = [];
selectors.forEach(selector => {
const found = document.querySelectorAll(selector);
debug(`使用选择器 "${selector}" 找到 ${found.length} 个元素`);
for (const element of found) {
if (!posts.includes(element)) {
posts.push(element);
}
}
});
// 过滤掉没有点赞和评论功能的帖子
return posts.filter(post => {
// 广告和特殊内容通常没有互动功能
if (post.classList.contains('f-single-biz') ||
post.querySelector('.f-single-top .arrow-down.adFeedsItem')) {
return false;
}
// 检查帖子是否有点赞和评论按钮
const hasLikeBtn = !!findLikeButton(post);
const hasCommentBtn = !!findCommentButton(post);
return hasLikeBtn && hasCommentBtn;
});
}
/**
* 查找帖子中的点赞按钮
* @param {Element} post - 帖子元素
* @returns {Element|null} 点赞按钮元素
*/
function findLikeButton(post) {
// 优先查找带有特定图标类的元素
const iconButton = post.querySelector('i.fui-icon.icon-op-praise');
if (iconButton) {
// 如果找到图标,获取其父元素(通常是a标签)
return iconButton.parentElement;
}
// 如果无法通过图标找到,尝试其他选择器
const selectors = [
'a[data-cmd="qz_like"]', // 标准点赞按钮
'.qz_like_btn', // 常见类名
'a[data-clicklog="like"]', // 数据属性
'.btn-praise', // 按钮类
'.item-like-btn', // 点赞按钮类
'.op-list a[data-link="0"]' // 操作列表中的链接
];
// 使用选择器查找
for (const selector of selectors) {
const button = post.querySelector(selector);
if (button) return button;
}
// 根据文本内容查找可能的点赞按钮
const allLinks = Array.from(post.querySelectorAll('a'));
for (const link of allLinks) {
if ((link.textContent.includes('赞') ||
link.innerText.includes('👍')) &&
!link.querySelector('*')) {
return link;
}
}
return null;
}
/**
* 验证点赞是否成功
* @param {Element} post - 帖子元素
* @param {Element} likeButton - 点赞按钮元素
* @returns {boolean} 点赞是否成功
*/
function verifyLikeSuccess(post, likeButton) {
// 首先检查按钮状态
if (isAlreadyLiked(likeButton)) {
return true;
}
// 检查帖子中的点赞数是否增加
const likeCount = post.querySelector('.op-list .item:first-child .user-counts');
if (likeCount && likeCount.innerText) {
// 增加了点赞数,或者显示"我和xxx人点赞"
if (likeCount.innerText.includes('我') || likeCount.innerText.includes('1')) {
return true;
}
}
// 检查点赞按钮颜色变化
const style = getComputedStyle(likeButton);
if (style.color === 'rgb(251, 114, 153)' || // 粉红色通常表示已点赞
style.color === 'rgb(250, 60, 76)') { // 红色也可能表示已点赞
return true;
}
return false;
}
/**
* 查找帖子中的评论按钮
* @param {Element} post - 帖子元素
* @returns {Element|null} 评论按钮元素
*/
function findCommentButton(post) {
// 基于用户提供的信息,优先查找带有特定图标类的元素
const iconButton = post.querySelector('i.fui-icon.icon-op-comment');
if (iconButton) {
// 如果找到图标,获取其父元素(通常是a标签)
return iconButton.parentElement;
}
// 如果无法通过图标找到,尝试其他选择器
const selectors = [
'a[data-cmd="qz_reply"]', // 标准评论按钮
'.qz_btn_reply', // 常见类名
'a[data-clicklog="comment"]', // 数据属性
'.btn-comment', // 按钮类
'.op-list a[data-link="1"]' // 操作列表中的链接
];
// 使用选择器查找
for (const selector of selectors) {
const button = post.querySelector(selector);
if (button) return button;
}
// 根据文本内容查找可能的评论按钮
const allLinks = Array.from(post.querySelectorAll('a'));
for (const link of allLinks) {
if (link.textContent.includes('评论') && !link.querySelector('*')) {
return link;
}
}
return null;
}
/**
* 查找评论输入框
* @param {Element} container - 容器元素,通常是帖子或评论区
* @returns {Element|null} 评论输入框元素
*/
function findCommentInput(container) {
// 基于用户提供的信息,优先查找contenteditable的div
const contentEditableInput = document.querySelector('div[contenteditable="true"].textinput.textarea.c_tx2');
if (contentEditableInput && isVisible(contentEditableInput)) {
return contentEditableInput;
}
// 扩大查找范围
const extendedContainer = container.closest('.f-single') || document;
// 查找评论输入框的各种可能选择器
const selectors = [
'div[contenteditable="true"].textinput', // 常见选择器
'div[contenteditable="true"].textarea.c_tx2', // 另一种形式
'.textinput[contenteditable="true"]', // 可编辑div
'div.textinput-default[contenteditable="true"]', // 另一种形式
'.comment-box textarea', // textarea形式
'.comments-box .textinput', // 其他形式
'textarea[placeholder*="评论"]', // 通过占位符查找
'.qz-editor-editable' // 特殊编辑器
];
// 使用选择器查找
for (const selector of selectors) {
const input = extendedContainer.querySelector(selector);
if (input && isVisible(input)) return input;
}
// 如果在帖子中找不到,尝试在整个文档中查找最近出现的
if (container !== document) {
for (const selector of selectors) {
const inputs = document.querySelectorAll(selector);
for (const input of inputs) {
if (isVisible(input)) return input;
}
}
}
return null;
}
/**
* 查找发送按钮
* @param {Element} container - 容器元素,通常是帖子或评论区
* @returns {Element|null} 发送按钮元素
*/
function findSendButton(container) {
// 优先查找带有特定类的发表按钮
const postButton = document.querySelector('a.btn-post.gb_bt');
if (postButton && isVisible(postButton)) {
return postButton;
}
// 扩大查找范围
const extendedContainer = container.closest('.f-single') || document;
// 查找发送按钮的各种可能选择器
const selectors = [
'a.btn-post.gb_bt', // 常见选择器
'.btn-post', // 按钮类
'.submit-comment', // 常见类名
'.comment-submit', // 另一种类名
'a[data-cmd="postComment"]', // 数据属性
'button[type="submit"]', // 提交按钮
'input[type="submit"]' // 提交输入
];
// 使用选择器查找
for (const selector of selectors) {
const button = extendedContainer.querySelector(selector);
if (button && isVisible(button)) return button;
}
// 如果在帖子中找不到,尝试在整个文档中查找
if (container !== document) {
for (const selector of selectors) {
const buttons = document.querySelectorAll(selector);
for (const button of buttons) {
if (isVisible(button)) return button;
}
}
}
// 根据文本内容查找可能的发送按钮
const allButtons = Array.from(document.querySelectorAll('a, button, input[type="button"]'));
for (const button of allButtons) {
if (button.textContent) {
// 发送按钮通常包含以下文本
if ((button.textContent.includes('发表') ||
button.textContent.includes('发送') ||
button.textContent.includes('确定')) &&
isVisible(button)) {
return button;
}
}
}
return null;
}
/**
* 填写评论输入框
* @param {Element} input - 输入框元素
* @param {string} text - 评论文本
*/
async function fillCommentInput(input, text) {
try {
// 确保元素可见
scrollToElement(input);
// 清除输入框中可能存在的占位文本
const placeholder = input.querySelector('a.c_tx3[alt="replybtn"]');
if (placeholder) {
placeholder.remove();
}
// 判断输入框类型
const isContentEditable = input.getAttribute('contenteditable') === 'true';
if (isContentEditable) {
// 可编辑div元素
input.focus();
// 清空内容
input.innerHTML = '';
try {
// 使用execCommand插入文本
document.execCommand('insertText', false, text);
} catch (e) {
// 如果execCommand失败,直接设置内容
input.textContent = text;
}
} else {
// textarea或input元素
input.value = '';
input.focus();
// 设置值并触发事件
input.value = text;
triggerEvent(input, 'input');
triggerEvent(input, 'change');
}
} catch (err) {
debug(`填写评论输入框时出错: ${err.message}`);
// 尝试最简单的方法
if (input.value !== undefined) {
input.value = text;
} else {
input.textContent = text;
}
}
}
/**
* 尝试通过回车键发送评论
* @param {Element} input - 输入框元素
* @returns {boolean} 是否成功发送
*/
function sendCommentByEnter(input) {
try {
// 创建回车事件
const enterEvent = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
keyCode: 13,
which: 13,
key: 'Enter'
});
// 触发回车事件
input.dispatchEvent(enterEvent);
return true;
} catch (err) {
debug(`通过回车键发送评论失败: ${err.message}`);
return false;
}
}
/**
* 生成评论内容
* @returns {string} 生成的评论文本
*/
function generateComment() {
// 从评论库中随机选择一条
if (config.commentTexts.length === 0) {
return "不错,支持一下!"; // 默认评论
}
const randomIndex = Math.floor(Math.random() * config.commentTexts.length);
return config.commentTexts[randomIndex];
}
/**
* 获取帖子的唯一ID
* @param {Element} post - 帖子元素
* @returns {string} 帖子ID
*/
function getPostId(post) {
// 尝试从ID属性获取
if (post.id && post.id.startsWith('fct_')) {
return post.id;
}
// 尝试从data-feedid属性获取
const feedId = post.getAttribute('data-feedid') ||
post.getAttribute('data-id');
if (feedId) {
return feedId;
}
// 查找可能包含feed ID的元素
const feedData = post.querySelector('[name="feed_data"]');
if (feedData) {
const fkey = feedData.getAttribute('data-fkey');
if (fkey) {
return fkey;
}
}
// 使用内容哈希作为后备
const content = post.innerText.slice(0, 100);
return hashString(content);
}
/**
* 生成哈希字符串
* @param {string} str - 输入字符串
* @returns {string} 哈希结果
*/
function hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为32位整数
}
return 'hash_' + Math.abs(hash).toString(16);
}
/**
* 设置动态监听
* 监听DOM变化,以便在加载新内容时触发处理
*/
function setupObserver() {
if (!window.MutationObserver) return;
// 创建观察器实例
observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// 检查是否添加了新的帖子
const hasFeedItems = Array.from(mutation.addedNodes).some(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return false;
return node.classList &&
(node.classList.contains('f-single') ||
node.classList.contains('feed-item') ||
node.id && node.id.startsWith('fct_'));
});
// 如果添加了新帖子,可能需要处理
if (hasFeedItems && stats.started) {
debug("检测到新的帖子被添加到页面");
// 不立即处理,等待变化完成
clearTimeout(observer.timeout);
observer.timeout = setTimeout(() => {
processNextBatch();
}, 1000);
}
}
}
});
// 配置观察选项
const config = { childList: true, subtree: true };
// 开始观察
observer.observe(document.body, config);
}
/**
* 滚动页面加载更多内容
* @returns {Promise
*/
async function scrollDown() {
const previousHeight = document.body.scrollHeight;
debug(`滚动页面加载更多内容(当前高度: ${previousHeight}px)`);
// 滚动页面
window.scrollBy(0, config.scrollDistance);
// 等待内容加载
await sleep(config.scrollInterval);
// 检查是否有新内容加载
const newHeight = document.body.scrollHeight;
const hasMore = newHeight > previousHeight;
debug(`滚动后页面高度: ${newHeight}px, ${hasMore ? '加载了新内容' : '没有加载新内容'}`);
return hasMore;
}
/**
* 检查元素是否可见
* @param {Element} element - 要检查的元素
* @returns {boolean} 元素是否可见
*/
function isVisible(element) {
if (!element) return false;
// 获取计算样式
const style = window.getComputedStyle(element);
// 检查元素是否不可见
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0' &&
element.offsetWidth > 0 &&
element.offsetHeight > 0;
}
/**
* 滚动到元素可见
* @param {Element} element - 要滚动到的元素
*/
function scrollToElement(element) {
if (!element) return;
try {
// 平滑滚动到元素
element.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
});
} catch (err) {
// 兼容性处理
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const isInViewport = rect.top >= 0 && rect.bottom <= windowHeight;
if (!isInViewport) {
window.scrollTo(0, rect.top + window.pageYOffset - windowHeight / 2);
}
}
}
/**
* 模拟点击元素
* @param {Element} element - 要点击的元素
*/
function clickElement(element) {
if (!element) return;
try {
// 尝试使用内置click方法
element.click();
} catch (err) {
// 兼容性处理:创建并分发点击事件
try {
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
element.dispatchEvent(event);
} catch (e) {
// 降级:使用旧版事件API
const event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
}
}
/**
* 触发DOM事件
* @param {Element} element - 目标元素
* @param {string} eventType - 事件类型
*/
function triggerEvent(element, eventType) {
if (!element) return;
try {
// 创建事件
const event = new Event(eventType, { bubbles: true });
// 触发事件
element.dispatchEvent(event);
} catch (err) {
// 降级处理
try {
const event = document.createEvent('HTMLEvents');
event.initEvent(eventType, true, true);
element.dispatchEvent(event);
} catch (e) {
debug(`触发事件失败: ${e.message}`);
}
}
}
/**
* 检查当前是否在QQ空间环境
* @returns {boolean} 是否在QQ空间
*/
function isQzoneEnvironment() {
return location.href.includes('qzone.qq.com');
}
/**
* 添加DOM扩展函数
* 为确保在不同浏览器环境下的兼容性
*/
function enhanceDom() {
// 如果Element原型没有remove方法,添加一个
if (!Element.prototype.remove) {
Element.prototype.remove = function() {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
};
}
}
/**
* 等待指定时间
* @param {number} ms - 等待毫秒数
* @returns {Promise} Promise对象
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 记录信息
* @param {...any} args - 要记录的参数
*/
function log(...args) {
console.log('【QQ空间助手】', ...args);
}
/**
* 记录调试信息
* @param {...any} args - 要记录的参数
*/
function debug(...args) {
if (config.debugMode) {
console.log('【调试】', ...args);
}
}
/**
* 记录错误信息
* @param {...any} args - 要记录的参数
*/
function error(...args) {
console.error('【错误】', ...args);
}
// =============== 公开API ===============
return {
// 核心功能
start,
stop,
// 配置修改
setCommentTexts: function(texts) {
if (Array.isArray(texts) && texts.length > 0) {
config.commentTexts = texts;
log(`已更新评论内容库,共${texts.length}条评论`);
return true;
}
return false;
},
addComment: function(text) {
if (typeof text === 'string' && text.trim()) {
config.commentTexts.push(text.trim());
log(`已添加新评论: "${text.trim()}"`);
return true;
}
return false;
},
setInterval: function(ms) {
if (ms > 0) {
config.actionInterval = ms;
log(`已设置操作间隔为 ${ms}ms`);
return true;
}
return false;
},
setMaxActions: function(num) {
config.maxActions = num;
log(`已设置最大操作数为 ${num || '不限'}`);
return true;
},
setLikeInterval: function(ms) {
if (ms >= 0) {
config.likeInterval = ms;
log(`已设置点赞操作等待时间为 ${ms}ms`);
return true;
}
return false;
},
setCommentDelay: function(ms) {
if (ms >= 0) {
config.commentDelay = ms;
log(`已设置点赞后评论延迟为 ${ms}ms`);
return true;
}
return false;
},
setDebugMode: function(enabled) {
config.debugMode = !!enabled;
log(`调试模式已${enabled ? '启用' : '禁用'}`);
return true;
},
// 状态查询
getStats: function() {
return {...stats};
},
getConfig: function() {
return {...config};
},
isRunning: function() {
return stats.started;
}
};
})();
// 控制台帮助信息
console.log(`
=================================================
🤖 QQ空间自动点赞+评论助手 已加载
=================================================
使用方法:
1. QzAllInOne.start() - 开始自动点赞+评论
2. QzAllInOne.stop() - 停止自动操作
自定义设置:
- QzAllInOne.setCommentTexts(['评论1', '评论2']) - 更新评论库
- QzAllInOne.addComment('新评论') - 添加单条评论
- QzAllInOne.setInterval(8000) - 设置每个帖子的操作间隔(毫秒)
- QzAllInOne.setMaxActions(10) - 设置最大操作数量
- QzAllInOne.setLikeInterval(1000) - 设置点赞后等待时间(毫秒)
- QzAllInOne.setCommentDelay(2000) - 设置点赞后评论延迟(毫秒)
- QzAllInOne.setDebugMode(true) - 开启调试模式
查询状态:
- QzAllInOne.getStats() - 获取当前统计信息
- QzAllInOne.isRunning() - 检查是否正在运行
=================================================
`);
安全提示
适度使用:请勿短时间内处理过多动态,以免被系统判定为异常行为。
内容多样化:尽量使用多样化的评论内容,避免重复相同内容。
注意隐私:在公共场所或共享电脑上使用时,注意个人隐私安全。
遵守规则:遵守QQ空间的使用规则,不要进行违规操作。
代码安全:只使用来自可信来源的脚本,避免运行未知来源的代码。
祝你使用愉快!如有问题,欢迎交流讨论。
文档最后更新:2025.5.17