添加 clearbluedot.js
This commit is contained in:
943
clearbluedot.js
Normal file
943
clearbluedot.js
Normal file
@ -0,0 +1,943 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name linux.do 消灭蓝点
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 1.0.0
|
||||||
|
// @description try to take over the world!
|
||||||
|
// @author Yous
|
||||||
|
// @match https://linux.do/*
|
||||||
|
// @exclude https://linux.do/*.json
|
||||||
|
// @icon https://cdn.linux.do/uploads/default/original/3X/9/d/9dd49731091ce8656e94433a26a3ef36062b3994.png
|
||||||
|
// @require https://cdn.jsdelivr.net/npm/layui@2.9.14/dist/layui.min.js
|
||||||
|
// @resource layuiCSS https://cdn.jsdelivr.net/npm/layui@2.9.14/dist/css/layui.min.css
|
||||||
|
// @grant GM_addStyle
|
||||||
|
// @grant GM_getResourceText
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
/* global layui */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 加载外部 CSS
|
||||||
|
let layuiCSS = GM_getResourceText("layuiCSS");
|
||||||
|
// 替换 CSS 中的相对路径
|
||||||
|
layuiCSS = layuiCSS.replace(/url\(\s*['"]?(\.\.\/font\/[^'"\s]+)['"]?\s*\)/g, function (match, url) {
|
||||||
|
// 假设字体文件位于一个可以访问的绝对路径
|
||||||
|
return match.replace(url, 'https://cdn.jsdelivr.net/npm/layui@2.9.14/dist/font/' + url.split('/').pop());
|
||||||
|
});
|
||||||
|
GM_addStyle(layuiCSS);
|
||||||
|
|
||||||
|
// ------ config ------
|
||||||
|
let CONFIG = {
|
||||||
|
enableBrowseAssist: true, // 开启助手
|
||||||
|
enableWindowPeriodRead: true, // 开启空闲阅读
|
||||||
|
readAllPostsInTopic: false, // 是否阅读主题所有帖子,false 从最后的内容开始看
|
||||||
|
singlePostsReading: 1000, // 单次阅读帖子数,控制 timings 请求 body 行为
|
||||||
|
maxRetryTimes: 5, // 最大重试次数
|
||||||
|
windowPeriodTopicUrls: ['https://linux.do/new.json', 'https://linux.do/unread.json'], // 空窗期获取帖子 url
|
||||||
|
getCsrfTokenFromHtml: false, // 是否从 html 中获取 csrf token
|
||||||
|
maxLogLineNum: 100, // 日志最大条数
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------ css ------
|
||||||
|
let uiWidth = "32rem"; // ui 宽度
|
||||||
|
let uiQueueHeight = "150px"; // ui 任务队列高度
|
||||||
|
let uiLogHeight = "300px"; // ui 日志高度
|
||||||
|
let uiTagFontSize = "0.75rem"; // ui 标签字体大小
|
||||||
|
let uiQueueFontSize = "0.75rem"; // ui 队列字体大小
|
||||||
|
let uiLogFontSize = "0.75rem"; // ui 日志字体大小
|
||||||
|
|
||||||
|
// ------ logic ------
|
||||||
|
let lastTaskTime = new Date("1970-01-01T00:00:00");
|
||||||
|
let statData = { // 维护统计
|
||||||
|
totalSuccess: 0,
|
||||||
|
totalFail: 0,
|
||||||
|
totalReadingTime: 0,
|
||||||
|
};
|
||||||
|
let taskQueue = []; // 维护任务队列
|
||||||
|
let windowPeriodTopics = []; // 空闲随机阅读的帖子列表,[[<topic_id>, <阅读楼层数>]]
|
||||||
|
let excludeTopic = []; // 将5分钟内阅读过的 topic 排除
|
||||||
|
let logs = []; // 维护日志
|
||||||
|
let globalCsrfToken = null; // 维护 csrf token
|
||||||
|
let nativeXMLHttpRequestOpen; // 维护原生 XMLHttpRequest 的 open 方法
|
||||||
|
let nativeXMLHttpRequestSend; // 维护原生 XMLHttpRequest 的 send 方法
|
||||||
|
let dialogElement = document.createElement("div"); // 维护 UI
|
||||||
|
let queueListElement = document.createElement("ul");
|
||||||
|
let logListElement = document.createElement("ul");
|
||||||
|
|
||||||
|
const isNativeFunction = (func) => {
|
||||||
|
if (typeof func !== "function") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取函数的字符串表示形式
|
||||||
|
const funcString = func.toString();
|
||||||
|
|
||||||
|
// 检查字符串是否包含 "[native code]"
|
||||||
|
return funcString.includes("[native code]");
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureNativeMethods = (func) => {
|
||||||
|
if (isNativeFunction(func)) {
|
||||||
|
return func;
|
||||||
|
} else {
|
||||||
|
throw new Error(`${func.name} is not native`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 随机整数
|
||||||
|
const getRandomInt = (start, end) => {
|
||||||
|
return Math.floor(Math.random() * (end - start + 1)) + start;
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchUrl = (url, pattern) => {
|
||||||
|
return pattern.test(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTopicUrl = (url) => {
|
||||||
|
const patternTopic = /^\/t\/[0-9]+\.json($|\?)/;
|
||||||
|
const patternPost = /^\/t\/[0-9]+\/[0-9]+\.json($|\?)/;
|
||||||
|
return matchUrl(url, patternPost) || matchUrl(url, patternTopic);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTimingsUrl = (url) => {
|
||||||
|
const patternTimings = "/topics/timings";
|
||||||
|
return url === patternTimings;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPollUrl = (url) => {
|
||||||
|
const patternPoll = /^\/message-bus\/[a-z0-9]+\/poll/;
|
||||||
|
return matchUrl(url, patternPoll);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (d) => {
|
||||||
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取预加载数据
|
||||||
|
const getPreloadedData = () => {
|
||||||
|
const preloadedDataElement = document.querySelector("#data-preloaded");
|
||||||
|
if (!preloadedDataElement) {
|
||||||
|
throw new Error("Preloaded data element not found");
|
||||||
|
}
|
||||||
|
const preloadedData = preloadedDataElement.getAttribute("data-preloaded");
|
||||||
|
return JSON.parse(preloadedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户名
|
||||||
|
const getUsername = () => {
|
||||||
|
const preloadedData = getPreloadedData();
|
||||||
|
const preloadedCurrentUserData = JSON.parse(preloadedData.currentUser);
|
||||||
|
return preloadedCurrentUserData.username;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取 csrf token
|
||||||
|
const getCsrfToken = async () => {
|
||||||
|
if (globalCsrfToken) {
|
||||||
|
return globalCsrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CONFIG.getCsrfTokenFromHtml) {
|
||||||
|
const csrfTokenElement = document.querySelector('meta[name="csrf-token"]');
|
||||||
|
if (!csrfTokenElement) {
|
||||||
|
throw new Error("CSRF token element not found");
|
||||||
|
}
|
||||||
|
globalCsrfToken = csrfTokenElement.getAttribute("content")
|
||||||
|
return globalCsrfToken;
|
||||||
|
} else {
|
||||||
|
const csrfRes = await fetch("https://linux.do/session/csrf", {
|
||||||
|
headers: {
|
||||||
|
"x-csrf-token": "undefined",
|
||||||
|
"x-requested-with": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
method: "GET",
|
||||||
|
mode: "cors",
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.catch((err) => { });
|
||||||
|
|
||||||
|
if (!csrfRes || !csrfRes.csrf) {
|
||||||
|
throw new Error("CSRF token not fetch");
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCsrfToken = csrfRes.csrf
|
||||||
|
return globalCsrfToken;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义一个函数,用于每隔1-2秒处理1000个阅读
|
||||||
|
const handleReadingPosts = async (
|
||||||
|
task,
|
||||||
|
topicId,
|
||||||
|
numbers,
|
||||||
|
csrfToken,
|
||||||
|
maxReadPosts = CONFIG.singlePostsReading,
|
||||||
|
retryTimes = 0
|
||||||
|
) => {
|
||||||
|
// 如果列表为空 或 超过最大限制 跳出 while
|
||||||
|
while (numbers.length > 0 && retryTimes <= CONFIG.maxRetryTimes) {
|
||||||
|
// 取出前 1000 个字符串,如果不足 1000 个,则取出所有剩余的字符串
|
||||||
|
let toProcess = numbers.slice(0, maxReadPosts);
|
||||||
|
toProcess = toProcess[0] === 0 ? toProcess.slice(1) : toProcess;
|
||||||
|
|
||||||
|
// 受后端限制,生成随机整数 randTime,范围在 60 秒到 61秒之间
|
||||||
|
const randTime = getRandomInt(60000, 61000);
|
||||||
|
// 使用模板生成新的字符串列表
|
||||||
|
const newStrings = toProcess.map((num) => `timings%5B${num}%5D=${randTime}`);
|
||||||
|
// 使用"&"连接字符串
|
||||||
|
const resultString = [...newStrings, `topic_time=${randTime}`, `topic_id=${topicId}`].join("&");
|
||||||
|
|
||||||
|
// 生成随机整数 睡眠时间,范围在 2000ms 到 3000ms 之间
|
||||||
|
await new Promise(resolve => setTimeout(resolve, getRandomInt(2000, 3000)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 请求 timing
|
||||||
|
const res = await fetch("https://linux.do/topics/timings", {
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
"x-csrf-token": csrfToken,
|
||||||
|
"x-requested-with": "XMLHttpRequest",
|
||||||
|
},
|
||||||
|
body: resultString,
|
||||||
|
method: "POST",
|
||||||
|
mode: "cors",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
addLog("success", `已完成话题[${topicId}]${toProcess[0]}至${toProcess[toProcess.length - 1]}层话题阅读`);
|
||||||
|
numbers = numbers.slice(maxReadPosts);
|
||||||
|
retryTimes = 0;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, getRandomInt(1000, 2000)));
|
||||||
|
} else if (res.status >= 400 && res.status < 600) {
|
||||||
|
addLog("warning", `阅读话题[${topicId}]出现错误(${res.status})!正在重试……`);
|
||||||
|
task.status = "retrying";
|
||||||
|
updateDialogQueue();
|
||||||
|
retryTimes++;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, getRandomInt(3000, 5000)));
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected status: ${res.status}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
retryTimes++;
|
||||||
|
addLog("error", `阅读话题[${topicId}]发生未知错误: ${err.message}`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, getRandomInt(3000, 5000)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryTimes > CONFIG.maxRetryTimes) {
|
||||||
|
return { topicId, error: true, detail: "超过最大重试次数" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { topicId, error: false, detail: "已完成阅读" };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 配置 对话框
|
||||||
|
const settingDialog = () => {
|
||||||
|
let layer = layui.layer;
|
||||||
|
let form = layui.form;
|
||||||
|
let $ = layui.$;
|
||||||
|
|
||||||
|
layer.open({
|
||||||
|
type: 1,
|
||||||
|
title: '设置',
|
||||||
|
shade: false,
|
||||||
|
area: ['34rem', '100%'],
|
||||||
|
offset: 'r',
|
||||||
|
anim: 'slideLeft',
|
||||||
|
move: false,
|
||||||
|
id: 'settingDialog-layer',
|
||||||
|
content: $("#settingDialog"),
|
||||||
|
});
|
||||||
|
// 初始化界面值
|
||||||
|
form.val('settingDialog-filter', {
|
||||||
|
"enableBrowseAssist": CONFIG.enableBrowseAssist,
|
||||||
|
"enableWindowPeriodRead": CONFIG.enableWindowPeriodRead,
|
||||||
|
"getCsrfTokenFromHtml": CONFIG.getCsrfTokenFromHtml,
|
||||||
|
"readAllPostsInTopic": CONFIG.readAllPostsInTopic,
|
||||||
|
"windowPeriodTopicUrls": CONFIG.windowPeriodTopicUrls.join(","),
|
||||||
|
"singlePostsReading": CONFIG.singlePostsReading,
|
||||||
|
"maxRetryTimes": CONFIG.maxRetryTimes,
|
||||||
|
"maxLogLineNum": CONFIG.maxLogLineNum,
|
||||||
|
});
|
||||||
|
// 保存设置事件
|
||||||
|
form.on('submit(saveSetting)', function (data) {
|
||||||
|
// 获取表单字段值
|
||||||
|
let field = data.field;
|
||||||
|
let enableBrowseAssist = field?.enableBrowseAssist && field?.enableBrowseAssist === "1" ? true : false;
|
||||||
|
// 助手开关是否被操作
|
||||||
|
let flag = enableBrowseAssist != CONFIG.enableBrowseAssist ? true : false;
|
||||||
|
|
||||||
|
CONFIG.enableBrowseAssist = enableBrowseAssist;
|
||||||
|
CONFIG.enableWindowPeriodRead = field?.enableWindowPeriodRead && field?.enableWindowPeriodRead === "1" ? true : false;
|
||||||
|
CONFIG.getCsrfTokenFromHtml = field?.getCsrfTokenFromHtml && field?.getCsrfTokenFromHtml === "1" ? true : false;
|
||||||
|
CONFIG.readAllPostsInTopic = field?.readAllPostsInTopic && field?.readAllPostsInTopic === "1" ? true : false;
|
||||||
|
CONFIG.windowPeriodTopicUrls = field?.windowPeriodTopicUrls && field?.windowPeriodTopicUrls.length > 0 ? field?.windowPeriodTopicUrls.split(",") : [];
|
||||||
|
CONFIG.singlePostsReading = field?.singlePostsReading ? field?.singlePostsReading : 1000;
|
||||||
|
CONFIG.maxRetryTimes = field?.maxRetryTimes ? field?.maxRetryTimes : 5;
|
||||||
|
CONFIG.maxLogLineNum = field?.maxLogLineNum ? field?.maxLogLineNum : 100;
|
||||||
|
|
||||||
|
let username = getUsername();
|
||||||
|
GM_setValue(`${username}_eliminate_blue_dot_config`, JSON.stringify(CONFIG));
|
||||||
|
layer.msg('保存成功', { icon: 1, offset: 'rt', anim: 'slideLeft' }, function () {
|
||||||
|
layer.closeLast('page');
|
||||||
|
addLog("success", "配置变更完成");
|
||||||
|
if (flag) {
|
||||||
|
if (CONFIG.enableBrowseAssist) {
|
||||||
|
helperStart();
|
||||||
|
} else {
|
||||||
|
helperStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false; // 阻止默认 form 跳转
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建对话框
|
||||||
|
const createDialog = () => {
|
||||||
|
const dialog = document.createElement("div");
|
||||||
|
dialog.id = "task-dialog";
|
||||||
|
dialog.className = "d-modal__container";
|
||||||
|
dialog.style.cssText =
|
||||||
|
`position: fixed; bottom: 4rem; right: 1rem; width: ${uiWidth}; border-radius: 0.5rem; display: none; z-index: 1000;`;
|
||||||
|
|
||||||
|
const header = document.createElement("div");
|
||||||
|
header.className = "d-modal__header";
|
||||||
|
header.style.cssText = "";
|
||||||
|
header.innerHTML =
|
||||||
|
'<div class="d-modal__title" style="flex: 1;"><h3 id="discourse-modal-title" class="d-modal__title-text">Task Queue & Logs</h3></div>';
|
||||||
|
|
||||||
|
const switchButton = document.createElement("button");
|
||||||
|
switchButton.className = "btn no-text btn-icon btn-flat";
|
||||||
|
switchButton.style = "flex: 0;";
|
||||||
|
switchButton.title = "设置";
|
||||||
|
switchButton.type = "button";
|
||||||
|
switchButton.innerHTML = `<svg class="fa d-icon d-icon-cog svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use href="#cog"></use></svg>`;
|
||||||
|
switchButton.onclick = () => {
|
||||||
|
settingDialog();
|
||||||
|
};
|
||||||
|
header.appendChild(switchButton);
|
||||||
|
|
||||||
|
const headerCloseButton = document.createElement("button");
|
||||||
|
headerCloseButton.className = "btn no-text btn-icon btn-flat modal-close";
|
||||||
|
headerCloseButton.style = "flex: 0;";
|
||||||
|
headerCloseButton.title = "关闭";
|
||||||
|
headerCloseButton.type = "button";
|
||||||
|
headerCloseButton.innerHTML =
|
||||||
|
'<svg class="fa d-icon d-icon-times svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use href="#times"></use></svg>';
|
||||||
|
headerCloseButton.onclick = () => {
|
||||||
|
dialog.style.display = dialog.style.display === "none" ? "block" : "none";
|
||||||
|
};
|
||||||
|
header.appendChild(headerCloseButton);
|
||||||
|
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.className = "d-modal__body";
|
||||||
|
content.style.cssText = "padding: 0.5rem; display: flex; flex-direction: column; height: calc(80vh - 4rem); overflow: hidden;";
|
||||||
|
|
||||||
|
const queueContainer = document.createElement("section");
|
||||||
|
queueContainer.id = "queue-container";
|
||||||
|
queueContainer.style.cssText = "flex: 1; margin-bottom: 1rem; width: 100%;";
|
||||||
|
queueContainer.innerHTML = "<h4>Queue</h4>";
|
||||||
|
const queueList = document.createElement("ul");
|
||||||
|
queueList.style.cssText = `height: ${uiQueueHeight}; overflow-y: auto; --scrollbarBg: transparent; --scrollbarThumbBg: var(--primary-low); --scrollbarWidth: 0.5em; scrollbar-color: rgba(0, 0, 0, 0.3) var(--scrollbarBg); margin: 1em 0 0 0.25em;`;
|
||||||
|
queueContainer.appendChild(queueList);
|
||||||
|
|
||||||
|
const logContainer = document.createElement("section");
|
||||||
|
logContainer.id = "log-container";
|
||||||
|
logContainer.style.cssText = "flex: 1; margin-bottom: 0; width: 100%;";
|
||||||
|
logContainer.innerHTML = "<h4>Logs</h4>";
|
||||||
|
const logList = document.createElement("ul");
|
||||||
|
logList.style.cssText = `height: ${uiLogHeight}; overflow-y: auto; --scrollbarBg: transparent; --scrollbarThumbBg: var(--primary-low); --scrollbarWidth: 0.5em; scrollbar-color: rgba(0, 0, 0, 0.3) var(--scrollbarBg); margin-left: 0.25em;`;
|
||||||
|
logContainer.appendChild(logList);
|
||||||
|
|
||||||
|
content.appendChild(queueContainer);
|
||||||
|
content.appendChild(logContainer);
|
||||||
|
|
||||||
|
const toggleButton = document.createElement("button");
|
||||||
|
toggleButton.title = "疯狂阅读";
|
||||||
|
toggleButton.className = "btn btn-default no-text btn-icon";
|
||||||
|
toggleButton.style.cssText =
|
||||||
|
"z-index: 1001; position: fixed; bottom: 1rem; right: 1rem;";
|
||||||
|
toggleButton.innerHTML =
|
||||||
|
'<svg class="fa d-icon svg-icon svg-string" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path></svg>';
|
||||||
|
toggleButton.onclick = () => {
|
||||||
|
dialog.style.display = dialog.style.display === "none" ? "block" : "none";
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(toggleButton);
|
||||||
|
dialog.appendChild(header);
|
||||||
|
dialog.appendChild(content);
|
||||||
|
document.body.appendChild(dialog);
|
||||||
|
|
||||||
|
// 设置界面
|
||||||
|
let settingHtml = `<div id="settingDialog" style="padding: 0.5rem;display: none;"><form class="layui-form layui-form-pane" action="" lay-filter="settingDialog-filter">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">开启助手</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" value="1" name="enableBrowseAssist" title="ON|OFF" lay-skin="switch">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">开启空闲阅读</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" value="1" name="enableWindowPeriodRead" title="ON|OFF" lay-skin="switch">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">从页面中获取csrfToken</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" value="1" name="getCsrfTokenFromHtml" title="ON|OFF" lay-skin="switch">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">阅读主题所有帖子</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="checkbox" value="1" name="readAllPostsInTopic" title="ON|OFF" lay-skin="switch">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">空闲阅读帖子url</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="windowPeriodTopicUrls" placeholder="多个以 , 分隔" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">单次阅读帖子数</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="number" name="singlePostsReading" autocomplete="off" placeholder="单次阅读帖子数" min="100" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">最大重试次数</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="number" name="maxRetryTimes" autocomplete="off" placeholder="最大重试次数" min="1"
|
||||||
|
max="10" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label" style="width: 45%;">日志最大条数</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="number" name="maxLogLineNum" autocomplete="off" placeholder="日志最大条数"
|
||||||
|
min="100" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<button class="layui-btn" lay-submit lay-filter="saveSetting">保存更改</button>
|
||||||
|
</div>
|
||||||
|
</form></div>`;
|
||||||
|
layui.$("body").append(settingHtml);
|
||||||
|
|
||||||
|
return { dialog, queueList, logList };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建 tag 样式
|
||||||
|
const createTagStyle = (tagType) => {
|
||||||
|
// 基础样式
|
||||||
|
const baseStatusStyle =
|
||||||
|
`font-size: ${uiTagFontSize}; line-height: 1rem; padding-top: .25rem; padding-bottom: .25rem; padding-left: .5rem; padding-right: .5rem; border-radius: .25rem;`;
|
||||||
|
|
||||||
|
let statusStyle;
|
||||||
|
switch (tagType) {
|
||||||
|
case "warning":
|
||||||
|
case "pending":
|
||||||
|
statusStyle = `${baseStatusStyle} color: rgba(146,64,14,1); background-color: rgba(253,230,138,1);`;
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
case "processing":
|
||||||
|
statusStyle = `${baseStatusStyle} color: rgba(30,64,175,1); background-color: rgba(191,219,254,1);`;
|
||||||
|
break;
|
||||||
|
case "success":
|
||||||
|
case "completed":
|
||||||
|
statusStyle = `${baseStatusStyle} color: rgba(6,95,70,1); background-color: rgba(167,243,208,1);`;
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
case "retrying":
|
||||||
|
case "failed":
|
||||||
|
statusStyle = `${baseStatusStyle} color: rgba(160,0,1,1); background-color: rgba(255,209,209,1);`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusStyle = `${baseStatusStyle} color: rgba(60,60,60,1); background-color: rgba(120, 120, 120,0.5);`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusStyle;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新对话框内容
|
||||||
|
const updateDialogLog = () => {
|
||||||
|
logListElement.innerHTML = logs
|
||||||
|
.map((log) => {
|
||||||
|
const eachStatusStyle = createTagStyle(log.level);
|
||||||
|
return `<li style="justify-content: space-between; align-items: center; display: flex; margin-bottom: 0.5rem;"><span style="font-size: ${uiLogFontSize};">${log.time} - ${log.message}</span><span style="${eachStatusStyle}">${log.level}</span></li>`;
|
||||||
|
})
|
||||||
|
.reverse()
|
||||||
|
.join("");
|
||||||
|
// logListElement.scrollTop = logListElement.scrollHeight;
|
||||||
|
};
|
||||||
|
const updateDialogQueue = () => {
|
||||||
|
const statRow = `<li style="justify-content: space-between; align-items: center; display: flex; margin-bottom: 0.5rem; padding: 0 0 .8em 0; border-bottom: 1px solid var(--primary-low);"><span style="${createTagStyle('info')}">待执行数:${taskQueue.length}</span><span style="${createTagStyle('success')}">成功数:${statData.totalSuccess}</span><span style="${createTagStyle('error')}">失败数:${statData.totalFail}</span><span style="${createTagStyle()}">未读帖子:${windowPeriodTopics.length}</span><span style="${createTagStyle('warning')}">阅读时间:${Math.round(statData.totalReadingTime / 60).toFixed(0)}分</span></li>`;
|
||||||
|
queueListElement.innerHTML = statRow + taskQueue
|
||||||
|
.map((task) => {
|
||||||
|
const eachStatusStyle = createTagStyle(task.status);
|
||||||
|
return `<li style="justify-content: space-between; align-items: center; display: flex; margin-bottom: 0.5rem;"><span style="font-size: ${uiQueueFontSize};">[${task.actionType}] ${task.topicId}</span><span style="${eachStatusStyle}">${task.status}</span></li>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
// queueListElement.scrollTop = queueListElement.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加日志
|
||||||
|
const addLog = (level, message) => {
|
||||||
|
if (logs.length >= CONFIG.maxLogLineNum) logs.shift();
|
||||||
|
logs.push({ time: formatDate(new Date()), level, message });
|
||||||
|
updateDialogLog();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 向队列中加入任务的方法
|
||||||
|
const addTask = (topicId, numbers, csrfToken, maxReadPosts, actionType) => {
|
||||||
|
if (CONFIG.enableBrowseAssist) {
|
||||||
|
// 检查队列中是否已存在相同的 topicId
|
||||||
|
const isDuplicate = taskQueue.some(task => task.topicId === topicId) || excludeTopic.some(e => e.topicId === topicId);
|
||||||
|
if (!isDuplicate) {
|
||||||
|
taskQueue.push({
|
||||||
|
topicId,
|
||||||
|
numbers,
|
||||||
|
csrfToken,
|
||||||
|
maxReadPosts,
|
||||||
|
actionType,
|
||||||
|
status: "pending",
|
||||||
|
});
|
||||||
|
addLog("info", `任务已添加,目前队列长度:${taskQueue.length}`);
|
||||||
|
updateDialogQueue();
|
||||||
|
|
||||||
|
// 如果这是队列中的第一个任务,立即开始处理
|
||||||
|
if (taskQueue.length === 1) {
|
||||||
|
processQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addInitTask = async () => {
|
||||||
|
if (windowPeriodTopics.length > 0) {
|
||||||
|
addLog("info", "空闲期任务开始执行");
|
||||||
|
const csrfToken = await getCsrfToken();
|
||||||
|
let indicesToRemove = [];
|
||||||
|
for (const [index, windowPeriodTopicSelected] of windowPeriodTopics.entries()) {
|
||||||
|
if (index > 29) {
|
||||||
|
// 每次往任务队列中 最多加30个任务
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
indicesToRemove.push(index);
|
||||||
|
|
||||||
|
let windowPeriodTopicId = windowPeriodTopicSelected[0];
|
||||||
|
let windowPeriodTopicNums = Array.from(
|
||||||
|
{ length: windowPeriodTopicSelected[1] },
|
||||||
|
(_, i) => i + 1
|
||||||
|
);
|
||||||
|
addTask(
|
||||||
|
windowPeriodTopicId,
|
||||||
|
windowPeriodTopicNums,
|
||||||
|
csrfToken,
|
||||||
|
CONFIG.singlePostsReading,
|
||||||
|
"无限阅读"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 移除已经加入任务列表的未读 topic
|
||||||
|
windowPeriodTopics = windowPeriodTopics.filter((_, index) => !indicesToRemove.includes(index));
|
||||||
|
|
||||||
|
if (windowPeriodTopics.length == 0) {
|
||||||
|
await addWindowPeriodTopics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理队列中任务的方法
|
||||||
|
const processQueue = async () => {
|
||||||
|
while (taskQueue.length > 0) {
|
||||||
|
const task = taskQueue[0];
|
||||||
|
addLog("info", `正在阅读:${task.topicId}`);
|
||||||
|
|
||||||
|
task.status = "processing";
|
||||||
|
updateDialogQueue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const readingRes = await handleReadingPosts(
|
||||||
|
task,
|
||||||
|
task.topicId,
|
||||||
|
task.numbers,
|
||||||
|
task.csrfToken,
|
||||||
|
task.maxReadPosts
|
||||||
|
);
|
||||||
|
|
||||||
|
const finishTime = new Date();
|
||||||
|
const timeDiff = (finishTime - lastTaskTime) / 1000;
|
||||||
|
|
||||||
|
if (readingRes.error) {
|
||||||
|
statData.totalFail += 1;
|
||||||
|
task.status = "failed";
|
||||||
|
addLog("error", readingRes.detail);
|
||||||
|
} else {
|
||||||
|
statData.totalReadingTime += Math.min(timeDiff, 60);
|
||||||
|
statData.totalSuccess += 1;
|
||||||
|
lastTaskTime = finishTime;
|
||||||
|
task.status = "completed";
|
||||||
|
addLog("success", `任务已完成:${task.topicId}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
statData.totalFail += 1;
|
||||||
|
task.status = "failed";
|
||||||
|
addLog("error", `处理任务时发生错误:${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
taskQueue.shift();
|
||||||
|
updateDialogQueue();
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProcessTopic = async (request) => {
|
||||||
|
// console.log("[LINUXDO NEXT] 这是一条帖子信息 Topic");
|
||||||
|
try {
|
||||||
|
const topicData = JSON.parse(request.response);
|
||||||
|
const csrfToken = await getCsrfToken();
|
||||||
|
const highestPostNumber = topicData.highest_post_number;
|
||||||
|
let lastReadPostNumber;
|
||||||
|
if (CONFIG.readAllPostsInTopic) {
|
||||||
|
lastReadPostNumber = 1; // 强制读所有
|
||||||
|
} else {
|
||||||
|
lastReadPostNumber = topicData.last_read_post_number; // 读最近的
|
||||||
|
}
|
||||||
|
// 创建包含 lastReadPostNumber 到 highestPostNumber 的整数列表
|
||||||
|
let postNumbers = Array.from(
|
||||||
|
{ length: highestPostNumber - lastReadPostNumber + 1 },
|
||||||
|
(_, i) => i + lastReadPostNumber
|
||||||
|
);
|
||||||
|
addTask(
|
||||||
|
topicData.id,
|
||||||
|
[...postNumbers],
|
||||||
|
csrfToken,
|
||||||
|
CONFIG.singlePostsReading,
|
||||||
|
"主动出击"
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(request.response);
|
||||||
|
console.log(err);
|
||||||
|
addLog("error", "未知错误,请查看控制台!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBlankString = (str) => {
|
||||||
|
return !str || str.trim() === "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractPollString = (str) => {
|
||||||
|
if (typeof str === "string") {
|
||||||
|
const response = str.trim();
|
||||||
|
if (isBlankString(response)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.includes("\n|")) {
|
||||||
|
const pollDataAll = response.split("\n|").map((pollData) => {
|
||||||
|
if (isBlankString(pollData)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(pollData);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
console.log(pollData);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pollDataAll;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return JSON.parse(response);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
console.log(response);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(str);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProcessPoll = (request) => {
|
||||||
|
console.log("[LinuxDo Assist] 这是一条拉取消息 poll");
|
||||||
|
console.log(typeof request.response);
|
||||||
|
if (!!request.response) {
|
||||||
|
const extractedData = extractPollString(request.response);
|
||||||
|
console.log(extractedData);
|
||||||
|
} else {
|
||||||
|
addLog("error", "哦豁,Poll(扑)了个空");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearExcludeTopicArray = () => {
|
||||||
|
if (excludeTopic.length > 0) {
|
||||||
|
// 5分钟之前的时间戳
|
||||||
|
let fiveMinutesAgo = Math.floor(Date.now() / 1000) - 5 * 60;
|
||||||
|
excludeTopic = excludeTopic.filter(item => item.readTime > fiveMinutesAgo);
|
||||||
|
addLog('success', '清理5分钟之前阅读过的 Topic');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addWindowPeriodTopics = async () => {
|
||||||
|
if (CONFIG.enableBrowseAssist && CONFIG.enableWindowPeriodRead) {
|
||||||
|
let page = 0;
|
||||||
|
let maxPage = 9; // 最多获取10页未读列表
|
||||||
|
|
||||||
|
if (CONFIG.windowPeriodTopicUrls.length === 0) {
|
||||||
|
CONFIG.windowPeriodTopicUrls.push(`https://linux.do/unread.json`);
|
||||||
|
}
|
||||||
|
const csrfToken = await getCsrfToken();
|
||||||
|
for (let topicUrl of CONFIG.windowPeriodTopicUrls) {
|
||||||
|
addLog('info', `开始获取[${topicUrl}]的帖子`);
|
||||||
|
let unreadTopic = await fetchUnreadTopic(csrfToken, topicUrl, page);
|
||||||
|
handleUnreadTopic(unreadTopic);
|
||||||
|
while (true) {
|
||||||
|
page++;
|
||||||
|
if (unreadTopic.more_topics_url) {
|
||||||
|
unreadTopic = await fetchUnreadTopic(csrfToken, topicUrl, page);
|
||||||
|
handleUnreadTopic(unreadTopic);
|
||||||
|
|
||||||
|
// 生成随机整数 randSleepTime,范围在 1000ms 到 1500ms 之间
|
||||||
|
const randSleepTime = getRandomInt(1000, 1500);
|
||||||
|
// 睡眠时间
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, randSleepTime));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (page >= maxPage) {
|
||||||
|
// 若未读列表大于10页则仅获取10页
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addLog('info', `获取[${topicUrl}]的帖子完成`);
|
||||||
|
}
|
||||||
|
addLog('info', `将未读Topic加入空闲阅读的列表,数量[${windowPeriodTopics.length}]`);
|
||||||
|
if (windowPeriodTopics.length === 0) {
|
||||||
|
const randSleepTime = getRandomInt(60000, 65000);
|
||||||
|
addLog('info', `睡眠 ${Math.floor(randSleepTime / 1000)} s 后重新获取未读Topic`);
|
||||||
|
// 睡眠时间, 范围在 60000ms 到 65000ms 之间
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, randSleepTime));
|
||||||
|
if (CONFIG.enableBrowseAssist && CONFIG.enableWindowPeriodRead && windowPeriodTopics.length === 0) {
|
||||||
|
addWindowPeriodTopics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDialogQueue();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnreadTopic = (unreadTopic) => {
|
||||||
|
for (let item of unreadTopic.topics) {
|
||||||
|
// if (item.unread_posts > 0) {
|
||||||
|
windowPeriodTopics.push([item.id, item.highest_post_number]);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchUnreadTopic = async (csrfToken, topicUrl, page) => {
|
||||||
|
let response = await fetchGetJson({
|
||||||
|
url: `${topicUrl}?page=${page}`,
|
||||||
|
csrfToken,
|
||||||
|
});
|
||||||
|
if (response.status === 200) {
|
||||||
|
let jsonData = response.detail;
|
||||||
|
return jsonData.topic_list || {};
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected status: ${response.status}, message: ${response.detail}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础 get 请求
|
||||||
|
*
|
||||||
|
* @param {{url: string; csrfToken?: string; otherHeaders?: any;}} props 请求参数
|
||||||
|
* @returns {Promise<{status: number; error: boolean; detail: any[]}>}
|
||||||
|
*/
|
||||||
|
const fetchGetJson = async (props) => {
|
||||||
|
let retryTimes = 0;
|
||||||
|
|
||||||
|
while (retryTimes <= CONFIG.maxRetryTimes) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(props.url, {
|
||||||
|
headers: {
|
||||||
|
...(props.csrfToken && {
|
||||||
|
"x-csrf-token": props.csrfToken,
|
||||||
|
}),
|
||||||
|
...(props.otherHeaders && { ...props.otherHeaders }),
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
method: props.method || 'GET',
|
||||||
|
mode: "cors",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
status: response.status,
|
||||||
|
error: false,
|
||||||
|
detail: data,
|
||||||
|
};
|
||||||
|
} else if (response.status >= 400 && response.status < 600) {
|
||||||
|
// 在 2-5 秒之间随机等待
|
||||||
|
await new Promise(resolve => setTimeout(resolve, getRandomInt(2000, 5000)));
|
||||||
|
retryTimes++;
|
||||||
|
console.warn(`fetchGetJson: ${genErrMsg(response.status)}`);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
status: response.status,
|
||||||
|
error: true,
|
||||||
|
detail: "未知错误"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 在 2-5 秒之间随机等待
|
||||||
|
await new Promise(resolve => setTimeout(resolve, getRandomInt(2000, 5000)));
|
||||||
|
retryTimes++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (retryTimes > CONFIG.maxRetryTimes) {
|
||||||
|
return { status: 500, error: true, detail: "超过最大重试次数" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const genErrMsg = (statusCode) => {
|
||||||
|
switch (statusCode) {
|
||||||
|
case 403:
|
||||||
|
return '无权限查看';
|
||||||
|
case 429:
|
||||||
|
return '频率限制';
|
||||||
|
case 500:
|
||||||
|
return '服务器内部错误';
|
||||||
|
case 503:
|
||||||
|
return '服务器尚未处于可以接受请求的状态';
|
||||||
|
default:
|
||||||
|
return '请求失败';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function enableXMLHttpRequestHooks() {
|
||||||
|
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
|
||||||
|
// 拦截open
|
||||||
|
this._custom_storage = { method, url };
|
||||||
|
return nativeXMLHttpRequestOpen.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
XMLHttpRequest.prototype.send = function (data) {
|
||||||
|
this.addEventListener(
|
||||||
|
"readystatechange",
|
||||||
|
function () {
|
||||||
|
// 拦截 response
|
||||||
|
if (this.readyState === 4) {
|
||||||
|
// 判断 topic
|
||||||
|
if (
|
||||||
|
isTopicUrl(this._custom_storage.url) &&
|
||||||
|
this._custom_storage.method === "GET"
|
||||||
|
) {
|
||||||
|
handleProcessTopic(this);
|
||||||
|
}
|
||||||
|
// // 判断 timings
|
||||||
|
// if (
|
||||||
|
// isTimingsUrl(this._custom_storage.url) &&
|
||||||
|
// this._custom_storage.method === "POST"
|
||||||
|
// ) {
|
||||||
|
// console.log("[LINUXDO NEXT] 这是一条阅读计时 timings");
|
||||||
|
// console.log(this);
|
||||||
|
// }
|
||||||
|
// 判断 poll
|
||||||
|
// if (
|
||||||
|
// isPollUrl(this._custom_storage.url) &&
|
||||||
|
// this._custom_storage.method === "POST"
|
||||||
|
// ) {
|
||||||
|
// handleProcessPoll(this);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
return nativeXMLHttpRequestSend.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableXMLHttpRequestHooks() {
|
||||||
|
XMLHttpRequest.prototype.open = nativeXMLHttpRequestOpen;
|
||||||
|
XMLHttpRequest.prototype.send = nativeXMLHttpRequestSend;
|
||||||
|
}
|
||||||
|
|
||||||
|
const helperStart = () => {
|
||||||
|
enableXMLHttpRequestHooks();
|
||||||
|
addLog('success', CONFIG.enableBrowseAssist ? '助手已开启' : '助手已关闭');
|
||||||
|
addLog('success', CONFIG.enableWindowPeriodRead ? '空闲阅读已开启' : '空闲阅读已关闭');
|
||||||
|
|
||||||
|
addWindowPeriodTopics();
|
||||||
|
};
|
||||||
|
|
||||||
|
const helperStop = () => {
|
||||||
|
disableXMLHttpRequestHooks();
|
||||||
|
if (taskQueue.length > 1) {
|
||||||
|
addLog('warning', '正在删除多余任务,仅保留最后进行的任务');
|
||||||
|
taskQueue = taskQueue.slice(0, 1);
|
||||||
|
}
|
||||||
|
if (windowPeriodTopics.length > 0) {
|
||||||
|
addLog('warning', '正在删除空闲阅读列表中的任务');
|
||||||
|
windowPeriodTopics = [];
|
||||||
|
}
|
||||||
|
addLog('error', '助手已停止');
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
nativeXMLHttpRequestOpen = ensureNativeMethods(
|
||||||
|
XMLHttpRequest.prototype.open
|
||||||
|
);
|
||||||
|
nativeXMLHttpRequestSend = ensureNativeMethods(
|
||||||
|
XMLHttpRequest.prototype.send
|
||||||
|
);
|
||||||
|
const uiElements = createDialog();
|
||||||
|
dialogElement = uiElements.dialog;
|
||||||
|
queueListElement = uiElements.queueList;
|
||||||
|
logListElement = uiElements.logList;
|
||||||
|
|
||||||
|
// 获取缓存中的配置
|
||||||
|
let username = getUsername();
|
||||||
|
let configCache = GM_getValue(`${username}_eliminate_blue_dot_config`, "");
|
||||||
|
if (configCache.length > 0) {
|
||||||
|
CONFIG = JSON.parse(configCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 空闲任务, 定时执行
|
||||||
|
// 生成随机整数 randSleepTime,范围在 10000ms 到 15000ms 之间
|
||||||
|
const randSleepTime = getRandomInt(10000, 15000);
|
||||||
|
setInterval(() => {
|
||||||
|
if (CONFIG.enableBrowseAssist && CONFIG.enableWindowPeriodRead && taskQueue.length == 0) {
|
||||||
|
addInitTask();
|
||||||
|
}
|
||||||
|
}, randSleepTime);
|
||||||
|
|
||||||
|
// 定时清理 排除topic 数组, 每5分钟调用一次
|
||||||
|
setInterval(clearExcludeTopicArray, 5 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
init();
|
||||||
|
// 开始
|
||||||
|
helperStart();
|
||||||
|
|
||||||
|
})();
|
Reference in New Issue
Block a user