// ==UserScript==
// @name 联通内网 - 智慧法治平台
// @namespace http://unicom.studio/
// @version 2025-05-15
// @description 创新与灵感的交汇处
// @author easterNday
// @match https://lawplatform.chinaunicom.cn/*
// @icon https://unicom.studio/Unicom.svg
// @grant GM_xmlhttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
// @require data:application/javascript,window.LAYUI_GLOBAL=%7Bdir:'https://unpkg.com/[email protected]/dist/'%7D
// @require https://unpkg.com/[email protected]/dist/layui.js
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
"use strict";
const PASSWORD = "lawtalkwebfront_";
const CONCURRENCY_LIMIT = 5;
// 状态管理对象
const state = {
authorization: localStorage.getItem("law_auth") || "", // 持久化存储[5](@ref)
isRunning: false,
currentPage: 1,
ready: false,
readLimit: parseInt(localStorage.getItem("law_read_limit")) || 500, // 阅读数量限制,0表示无限制
processedCount: 0, // 已处理的文章数量
minDelay: parseInt(localStorage.getItem("law_min_delay")) || 1000, // 最小延迟时间(毫秒)
maxDelay: parseInt(localStorage.getItem("law_max_delay")) || 3000, // 最大延迟时间(毫秒)
limitReached: false, // 是否已达到阅读限制
};
// 用户信息
const userInfo = {
name: "",
pageSize: localStorage.getItem("law_page_size") || 50,
};
const articleStore = {
list: new Map(),
total: 0,
get count() {
return this.list.size;
},
};
// 请求拦截
(function setupRequestInterceptor() {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSetHeader = XMLHttpRequest.prototype.setRequestHeader;
// 请求头拦截
XMLHttpRequest.prototype.requestHeaders = {};
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
this.requestHeaders[name] = value;
originalSetHeader.apply(this, arguments);
};
// 核心拦截逻辑
XMLHttpRequest.prototype.open = function (method, url) {
this.addEventListener("load", function () {
// 仅捕获Authorization字段
if (this.requestHeaders) {
const authHeader =
this.requestHeaders["Authorization"] ||
this.requestHeaders["authorization"];
if (authHeader && authHeader !== state.authorization) {
state.authorization = authHeader;
localStorage.setItem("law_auth", authHeader);
}
}
if (url.includes("readPurview") && !userInfo.name) {
const mm = new URL(url).searchParams.get("mm");
userInfo.name = cryptoUtils.decryptECB(atob(mm));
}
});
originalOpen.apply(this, arguments);
};
})();
// 加密模块
const cryptoUtils = {
encryptECB: (text) => {
const key = CryptoJS.enc.Utf8.parse(PASSWORD.padEnd(16, "\0"));
return CryptoJS.AES.encrypt(text, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).toString();
},
decryptECB: (cipherText) => {
const key = CryptoJS.enc.Utf8.parse(PASSWORD.padEnd(16, "\0"));
return CryptoJS.AES.decrypt(cipherText, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).toString(CryptoJS.enc.Utf8);
},
};
// 请求队列系统
const requestQueue = {
queue: [],
active: 0,
async add(task) {
this.queue.push(task);
return this.process();
},
async process() {
return new Promise((resolve) => {
const runner = async () => {
while (this.active < CONCURRENCY_LIMIT && this.queue.length) {
const task = this.queue.shift();
this.active++;
try {
await task();
} catch (error) {
console.error("Task failed:", error);
} finally {
this.active--;
runner();
}
}
if (this.queue.length === 0 && this.active === 0) {
resolve();
}
};
runner();
});
},
};
// 数据获取逻辑
const dataService = {
async getTotal() {
const params = this.buildParams(1);
const { total } = await this.fetchData(params);
articleStore.total = total;
return total;
},
async fetchPage(pageNum) {
const params = this.buildParams(pageNum);
const { rows } = await this.fetchData(params);
rows.forEach((article) => {
articleStore.list.set(article.articleId, article);
});
return rows;
},
buildParams(pageNum) {
return {
pageNum,
pageSize: userInfo.pageSize,
sortType: 0,
columnOne: null,
columnTwo: null,
readPurviewValueList: [
"52afcdceba40421e85ae255d84611ce1",
"c9f4122a68904299b737555e2c13858d",
"fb9a92b14df74671840dc9c5bce47b22",
"42",
"4211",
"421121998",
],
readPurviewRoleTypeList: [],
m_m: userInfo.name,
};
},
async fetchData(params) {
const mm = cryptoUtils.encryptECB(JSON.stringify(params));
const response = await fetch(
`https://lawplatform.chinaunicom.cn/api/legal/more/article/list?pageSize=${userInfo.pageSize}&pageNum=${params.pageNum}`,
{
method: "POST",
headers: {
authorization: state.authorization,
"content-type": "application/json",
},
body: JSON.stringify({ mm }),
}
);
return response.json();
},
};
// 生成随机延迟时间
function getRandomDelay() {
return (
Math.floor(Math.random() * (state.maxDelay - state.minDelay + 1)) +
state.minDelay
);
}
// 延迟执行
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 文章处理
async function processArticles() {
try {
// 重置状态
state.processedCount = 0;
state.limitReached = false;
const totalPages = Math.ceil(articleStore.total / userInfo.pageSize);
// 创建一个新的请求队列用于文章处理
const articleProcessQueue = {
queue: [],
active: 0,
async add(task) {
this.queue.push(task);
return this.process();
},
async process() {
return new Promise((resolve) => {
const runner = async () => {
while (this.active < CONCURRENCY_LIMIT && this.queue.length) {
const task = this.queue.shift();
this.active++;
try {
await task();
} catch (error) {
console.error("Task failed:", error);
} finally {
this.active--;
runner();
}
}
if (this.queue.length === 0 && this.active === 0) {
resolve();
}
};
runner();
});
},
};
// 处理单个文章的函数
const processArticle = async (article) => {
// 检查是否已达到阅读限制
if (
state.readLimit > 0 &&
(state.processedCount >= state.readLimit || state.limitReached)
) {
return;
}
// 增加计数并检查是否刚好达到限制
state.processedCount++;
if (
state.readLimit > 0 &&
state.processedCount >= state.readLimit &&
!state.limitReached
) {
state.limitReached = true;
// 清空文章处理队列,停止后续处理
articleProcessQueue.queue = [];
}
const delayTime = getRandomDelay();
await delay(delayTime);
// 先处理阅读
await processRead(article);
await delay(delayTime);
// 再处理点赞,并获取结果
const likeResult = await processLike(article);
// 如果是已点赞的文章,减少计数(因为之前已经增加了)
if (likeResult && likeResult.isAlreadyLiked) {
// 已点赞的文章不计入处理数量
state.processedCount--;
// 如果减少后低于限制,取消限制标志
if (
state.limitReached &&
state.readLimit > 0 &&
state.processedCount < state.readLimit
) {
state.limitReached = false;
}
}
};
// 处理一页文章的函数
const processPage = async (pageNum) => {
const articles = await dataService.fetchPage(pageNum);
// 立即开始处理这一页的文章
for (const article of articles) {
if (state.readLimit > 0 && state.processedCount >= state.readLimit) {
if (!state.limitReached) {
state.limitReached = true;
}
return;
}
// 不等待添加完成,让文章处理真正并行
articleProcessQueue.add(() => processArticle(article));
}
};
// 创建页面处理任务并逐个添加,以便在达到限制时停止
for (let i = 0; i < totalPages; i++) {
// 如果已达到阅读限制,停止添加更多页面任务
if (state.readLimit > 0 && state.limitReached) {
break;
}
// 添加单个页面处理任务
requestQueue.queue.push(() => processPage(i + 1));
// 每添加一批任务后处理一次队列
if (
requestQueue.queue.length >= CONCURRENCY_LIMIT ||
i === totalPages - 1
) {
await requestQueue.process();
// 如果在处理过程中达到了限制,停止添加更多页面
if (state.readLimit > 0 && state.limitReached) {
break;
}
}
}
// 等待所有文章处理完成
await articleProcessQueue.process();
layer.msg(`操作已完成,共处理 ${state.processedCount} 篇文章`, {
icon: 1,
time: 7777,
});
} catch (error) {
layer.msg("处理失败: " + error.message, { icon: 2 });
}
}
// 阅读处理
async function processRead(article) {
// if (article.isBrowse) return;
const timestamp = cryptoUtils.encryptECB(Date.now().toString());
const mm = cryptoUtils.encryptECB(
JSON.stringify({
businessId: article.articleId,
m_m: userInfo.name,
})
);
await Promise.all([
fetch(
`https://lawplatform.chinaunicom.cn/api/legal-console/integral/checkIntegralForBrowser`,
{
method: "POST",
headers: {
authorization: state.authorization,
"content-type": "application/json",
},
body: JSON.stringify({ mm }),
}
),
// fetch(`https://lawplatform.chinaunicom.cn/api/legal/article/details/readPurview?timeStamp=${btoa(timestamp)}&mm=${btoa(mm)}`, {
// headers: { authorization: state.authorization }
// }),
fetch(
`https://lawplatform.chinaunicom.cn/api/legal/article/details/add-browse-num/${article.articleId}`,
{
headers: { authorization: state.authorization },
}
),
]);
// 显示阅读完成提示
layer.msg(
`已阅读文章: ${article.title.substring(0, 15)}${
article.title.length > 15 ? "..." : ""
}`,
{
time: 1500,
offset: "rt",
}
);
}
// 点赞处理
async function processLike(article) {
// if (article.isLike) return;
const mm = cryptoUtils.encryptECB(
JSON.stringify({
articleId: article.articleId,
whetherLike: 1,
m_m: userInfo.name,
})
);
const response = await fetch(
"https://lawplatform.chinaunicom.cn/api/legal/like-detail",
{
method: "POST",
headers: {
authorization: state.authorization,
"content-type": "application/json",
},
body: JSON.stringify({ mm }),
}
);
const data = await response.json();
const isAlreadyLiked = data.msg === "已点赞";
// 显示点赞完成提示,但如果是已点赞则显示不同消息
if (!isAlreadyLiked) {
layer.msg(
`已点赞文章: ${article.title.substring(0, 15)}${
article.title.length > 15 ? "..." : ""
}`,
{
time: 1500,
offset: "rt",
}
);
}
return { isAlreadyLiked }; // 返回是否是已点赞的状态
}
// 初始化入口
async function init() {
// setupRequestInterceptor();
await waitForUserInfo().then(() => {
layer.msg("基础信息获取完成", { time: 7777, icon: 1 });
});
await dataService.getTotal();
await createUI();
}
async function waitForUserInfo() {
return new Promise((resolve) => {
const check = () => {
if (userInfo.name && state.authorization) {
state.ready = true;
resolve();
} else {
setTimeout(check, 500);
}
};
check();
});
}
// 创建UI
async function createUI() {
// TODO: 完善UI
let buttonSets = document.createElement("div");
document.body.appendChild(buttonSets);
buttonSets.id = "buttonSets";
buttonSets.style.padding = "8px";
buttonSets.style.display = "flex";
buttonSets.style.flexDirection = "row";
buttonSets.style.justifyContent = "center";
buttonSets.style.alignItems = "center";
buttonSets.style.position = "fixed";
buttonSets.style.bottom = "20px";
buttonSets.style.right = "20px";
buttonSets.style.zIndex = "1000";
buttonSets.innerHTML = `
<link href="//unpkg.com/[email protected]/dist/css/layui.css" rel="stylesheet">
<button id="controlPanelButton" type="button" class="layui-btn layui-btn-lg layui-btn-primary layui-btn-radius layui-bg-red">控制面板</button>
`;
let controlPanelButton = document.getElementById("controlPanelButton");
controlPanelButton.onclick = () => {
layer.open({
type: 1,
id: "settingsPanel",
title: "智慧法治平台配置",
area: ["320px", "100%"],
offset: "l",
anim: "slideRight", // 从左往右
shade: 0.1,
// skin: 'layui-layer-win10',
icon: 6,
resize: false,
move: false,
content: `
<div class="layui-input-group">
<div class="layui-input-prefix">用户昵称</div>
<input type="text" name="username" value="" lay-verify="required" placeholder="${userInfo.name}" class="layui-input" disabled>
</div>
<div class="layui-input-group">
<div class="layui-input-prefix">文章总数</div>
<input type="text" name="username" value="" lay-verify="required" placeholder="${articleStore.total}" class="layui-input" disabled>
</div>
<div class="layui-input-group">
<div class="layui-input-prefix">单次请求数目</div>
<input id="pageSize" type="number" lay-affix="number" value="${userInfo.pageSize}" step="10" min="10" max="50" class="layui-input">
<!-- <div class="layui-input-suffix">单次请求数目</div> -->
</div>
<div class="layui-input-group">
<div class="layui-input-prefix">阅读文章限制</div>
<input id="readLimit" type="number" lay-affix="number" value="${state.readLimit}" step="10" min="0" class="layui-input">
<div class="layui-input-suffix">0表示无限制</div>
</div>
<div class="layui-input-group">
<div class="layui-input-prefix">最小延迟(毫秒)</div>
<input id="minDelay" type="number" lay-affix="number" value="${state.minDelay}" step="500" min="0" class="layui-input">
</div>
<div class="layui-input-group">
<div class="layui-input-prefix">最大延迟(毫秒)</div>
<input id="maxDelay" type="number" lay-affix="number" value="${state.maxDelay}" step="500" min="0" class="layui-input">
</div>
`,
btn: ["保存设置", "开始运行"],
btn1: function (index, layero, that) {
localStorage.setItem(
"law_page_size",
document.getElementById("pageSize").value
);
userInfo.pageSize = document.getElementById("pageSize").value;
// 保存阅读限制
const readLimit = document.getElementById("readLimit").value;
state.readLimit = parseInt(readLimit) || 0;
localStorage.setItem("law_read_limit", state.readLimit);
// 保存延迟设置
const minDelay = document.getElementById("minDelay").value;
const maxDelay = document.getElementById("maxDelay").value;
state.minDelay = parseInt(minDelay) || 1000;
state.maxDelay = parseInt(maxDelay) || 3000;
// 确保最小延迟不大于最大延迟
if (state.minDelay > state.maxDelay) {
state.minDelay = state.maxDelay;
}
localStorage.setItem("law_min_delay", state.minDelay);
localStorage.setItem("law_max_delay", state.maxDelay);
layer.msg("设置已保存", { icon: 1 });
return false;
},
btn2: function (index, layero, that) {
processArticles();
return true;
},
});
};
}
window.addEventListener("load", init);
})();