// ==UserScript==
// @name mooc讨论区自动完成
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 自动复制已有的评论并提交发布
// @author Yuguo
// @match *://www.icourse163.org/learn/*
// @match *://www.icourse163.org/spoc/learn/*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 创建一个日志窗口
let logWindow = null;
function openLogWindow() {
logWindow = window.open('', 'LogWindow', 'width=600,height=400');
logWindow.document.write('<html><head><title>日志输出</title></head><body><pre id="logArea"></pre></body></html>');
}
function log(message, color = 'black') {
if (!logWindow) openLogWindow();
const logArea = logWindow.document.getElementById('logArea');
// 使用 HTML 标签来支持颜色
const coloredMessage = `<span style="color:${color};">${message}</span>`;
logArea.innerHTML += coloredMessage + '<br>';
logArea.scrollTop = logArea.scrollHeight; // 滚动到最新的日志
}
// 配置项
const CONFIG = {
name:"",
staytime: 1000, // 停顿时间,单位:毫秒
commentIndex: 4, // 默认复制评论区域索引
interval: 1000, // 翻页间隔,单位:毫秒
baseUrl: window.location.href.split('#')[0], // 基础 URL
hashBase: '#/learn/forumindex', // 哈希部分
queryParam: 'p', // 翻页的参数名称
additionalParams: 't=0', // 其他固定参数
initialPage: 1, // 起始页码
nameAddress:'img#my-img',//自己的用户名地址
nameAlt:'alt',//自己的用户名标签
commentSectionsAddress:'.f-richEditorText.j-content',//该页所有回复
commentInputAddress:'.j-editor .ql-editor',//文本框
submitButtonAddress:'.j-edit-btn', //输入按钮
titleAddress :'.j-title.title.f-fl',//该页所有主题标题
commentLinksAddress:'a.f-fcgreen',//该页回复界面的姓名(包括老师)
linkAddress:'.f-fc3.f-f0.lb10.j-link',//该页的所有的主题(包括我关注的主题)
listItemsAddress :'.m-flwrap .m-data-lists .u-forumli',//该页的“全部主题”(不包括我关注的主题)
};
// 获取当前页面的哈希部分和当前页码
let currentHash = window.location.hash;
let currentPage = CONFIG.initialPage;
if (currentHash.includes(CONFIG.queryParam + '=')) {
let urlParams = new URLSearchParams(currentHash.split('?')[1]);
currentPage = parseInt(urlParams.get(CONFIG.queryParam)) || CONFIG.initialPage;
}
CONFIG.name = document.querySelector(CONFIG.nameAddress).getAttribute(CONFIG.nameAlt);
// 自动点击网站列表中的链接
function autoClickLinks() {
var listItems = document.querySelectorAll(CONFIG.listItemsAddress);
var currentIndex = 0;
// 页面跳转后的回调函数
function handlePageLoad() {
log("资源加载完成");
// 执行评论操作
autoPostComment().then(() => {
log("继续执行其他操作");
// 更新索引并跳转到下一个链接
currentIndex++;
if (currentIndex < listItems.length) {
clickNextLink();
} else {
log("所有网站链接已点击完,准备翻页");
waitForCommentPostAndNextPage();
}
}).catch((error) => {
log("评论发布失败: " + error,'red');
// 更新索引并跳转到下一个链接
currentIndex++;
if (currentIndex < listItems.length) {
clickNextLink();
} else {
log("所有网站链接已点击完,准备翻页");
waitForCommentPostAndNextPage();
}
});
}
async function clickNextLink() {
if (currentIndex < listItems.length) {
var item = listItems[currentIndex];
var link = item.querySelector(CONFIG.linkAddress);
if (link) {
var url = link.href;
log("正在跳转到: " + url);
// 使用 window.location.href 跳转到新页面,并确保在页面加载完成后执行后续操作
window.location.href = url;
// 设置延时,等待页面加载完成后再执行 handlePageLoad
setTimeout(() => {
handlePageLoad(); // 在页面加载完成后执行
}, CONFIG.staytime);
}
} else {
log("所有网站链接已点击完,准备翻页");
// 等待评论发布完成后再翻页
waitForCommentPostAndNextPage();
}
}
clickNextLink();
}
//找到长度最大的评论
function getCommentTextByMaxLengthInTopNExcludingFirst(commentSections, n) {
if (!commentSections || commentSections.length <= 1 || n <= 0) {
return "1"; // 数组无效、长度不足,或者 n 非法时返回 null
}
// 排除第一个元素并取前 n 个
var sectionsToConsider = Array.from(commentSections).slice(1, n);
if (sectionsToConsider.length === 0) {
return "1"; // 如果没有需要考虑的元素
}
var maxLength = 0;
var maxLengthElement = null;
sectionsToConsider.forEach(function (section) {
var length = (section.textContent || '').length;
if (length > maxLength) {
maxLength = length;
maxLengthElement = section;
}
});
return maxLengthElement ? maxLengthElement.textContent : "1";
}
function autoPostComment() {
return new Promise((resolve, reject) => {
// 确保页面加载完成
document.addEventListener("DOMContentLoaded", function () {
log("HTML 文档加载完成");
});
setTimeout(function () {
}, 500);
// 等待页面加载完成并确保评论区域和按钮可用
var commentSections = document.querySelectorAll(CONFIG.commentSectionsAddress);
//最大可以复制的长度
var maxlength = CONFIG.commentIndex;
if (commentSections.length <= CONFIG.commentIndex){
maxlength = commentSections.length ;
}
let commentText = getCommentTextByMaxLengthInTopNExcludingFirst(commentSections,maxlength);
var commentInput = document.querySelector(CONFIG.commentInputAddress);
var submitButton = document.querySelector(CONFIG.submitButtonAddress);
let title = document.querySelector(CONFIG.titleAddress);
log("标题: " + title.textContent);
if (commentSections.length >= 2) {
// 检查评论区是否已有评论
if (isCommentAlreadyPosted()) {
log("此帖子已回复过,跳过发布",'green');
return resolve("评论已发布,跳过");
}
if (commentInput && submitButton) {
commentInput.innerHTML = commentText;
log("正在发布评论...");
// 使用 setTimeout 模拟评论提交的异步操作
setTimeout(function () {
submitButton.click(); // 执行点击事件
resolve("评论发布完成"); // 提示评论已发布,resolve 回调
log("评论发布完成",'green');
}, 100);
} else {
reject("评论输入框或发布按钮未找到");
}
} else {
reject("没有足够的评论区域,无法复制评论");
}
let result = commentText.slice(0, 10).padEnd(10, '0');
log("内容: " + result+"……");
});
}
// 检查该帖子是否已经评论过
function isCommentAlreadyPosted() {
const username = CONFIG.name; // 固定的用户名
const commentLinks = document.querySelectorAll(CONFIG.commentLinksAddress);
for (let link of commentLinks) {
if (link && link.title === username) {
return true; // 如果找到已有评论,返回 true
}
}
return false; // 如果没有找到该用户名的评论,则返回 false
}
// 等待页面元素加载
function waitForElement(selector) {
const observer = new MutationObserver((mutationsList, observer) => {
if (document.querySelector(selector)) {
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// 等待评论发布完成并跳转到下一页
function waitForCommentPostAndNextPage() {
try {
// 等待评论发布的反馈(例如:通过页面上某个元素标记发布成功)
waitForElement('.f-richEditorText.j-content'); // 等待页面评论区域更新
log("评论已成功发布,准备翻页",'green');
// 自动翻到下一页
setTimeout(function() {
autoNextPage();
location.reload();
}, 1000);
} catch (error) {
log("等待评论发布或翻页时出错: " + error,'red');
}
}
// 自动翻到下一页
function autoNextPage() {
try {
log(`当前页码: ${currentPage}`);
currentPage++;
const newHash = `${CONFIG.hashBase}?${CONFIG.additionalParams}&${CONFIG.queryParam}=${currentPage}`;
const nextPageUrl = `${CONFIG.baseUrl}${newHash}`;
log(`跳转到下一页: ${nextPageUrl}`);
window.location.replace(nextPageUrl); // 跳转到新页面
setTimeout(function() {
location.reload(true);
}, 3000);
} catch (error) {
log("翻页时出错: " + error,'red');
}
}
// 检查是否有网站链接,如果没有则等待一段时间后重新检查
function checkForLinks() {
var listItems = document.querySelectorAll(CONFIG.listItemsAddress);
if (listItems.length === 0) {
log("没有找到网站链接,请刷新或者等待脚本",'#B8860B');
setTimeout(function() {
checkForLinks(); // 没有找到链接时,等待一段时间后重新检查
}, 10000);
return; // 停止当前函数的执行,等待下一次检查
}
autoClickLinks(); // 如果找到了网站链接,自动点击链接
}
// 确保页面加载完成后执行
window.onload = function() {
setTimeout(function() {
checkForLinks(); // 检查是否有网站链接
}, CONFIG.staytime); // 页面加载后等待一段时间再执行检查
};
log("------------hello--------------",'green');
})();