// ==UserScript==
// @name MCBBS抓取新帖mod
// @namespace io.github.msbbc
// @match *://www.mcbbs.net/*
// @icon https://beta.mcbbs.net/favicon.ico
// @version 0.7.4.7
// @author axototl
// @license AGPL-3.0-or-later
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_addValueChangeListener
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_notification
// @grant GM_info
// @grant GM_openInTab
// @require https://update.greasyfork.org/scripts/476522/1285584/Config_Manager.js
// @require https://update.greasyfork.org/scripts/478724/1284835/Notify.js
// @description 获取版块最新帖子。
// ==/UserScript==
'use strict';
const vio = (() => {
if (GM_info.scriptHandler === "Violentmonkey") return true;
GM_addElement("p", {
textContent: `脚本 ${GM_info.script.name} 优先适配暴力猴, 建议使用Violentmonkey(暴力猴)获得更好体验.`,
style: "position: fixed; bottom: 0; right: 0; color: #F92672; font-size: 16px; font-weight: bold;"
}).onclick = () => GM_openInTab("https://violentmonkey.github.io/get-it/");
return false;
})();
// JS Parser
const parse_result = (() => {
const patt = /^var\s+newthread.*?$/m; // 用于匹配JS代码开头
const numpatt = />([\d\-]+)<\/a>/; // 用于匹配回复数
const titler = />([^<]+)<\/a>$/; // 用于匹配标题
const ss = /href="([^>]+)">([^<]+)<\/a><\/em>/; // 用于匹配最后回复的URL
const lastposter_match = />([^<]*?)<\/a><\/cite>/; //最后回帖者
const match_icn = /src="(.*?)"/; // 帖子的icon
return js =>
(() => {
const matches = js.match(patt);
if (matches && matches.length > 0) {
js = matches[0];
const r = // 缓解性能问题
(0, eval)(`let table,removetbodyrow=()=>0;${js};newthread`);
if (config.debug) console.debug(r);
return r;
}
})()?.map(item => {
const item2 = {};
item2.title = titler.exec(item.thread.common.val)[1];
item2.reply_num = numpatt.exec(item.thread.num.val)[1] | 0;
item2.uri = "https://www.mcbbs.net/";
let tmp = ss.exec(item.thread.lastpost.val);
item2.lastpost_time = tmp[2];
item2.lastposter = lastposter_match.exec(item.thread.lastpost.val)[1];
item2.tid = item.tid;
if (config.jmp_to_lastpost) {
const uri = tmp[1].replaceAll("&", "&");
item2.uri += uri;
} else {
item2.uri += "thread-" + item.tid + "-1-1.html";
}
tmp = match_icn.exec(item.thread.icn.val);
if (tmp) item2.icon = tmp[1];
else item2.icon = "https://www.mcbbs.net/favicon.ico";
return item2;
});
})();
[
{
name: "experimental",
type: "bool",
desc: "实验性功能",
default: false,
// 当前版本暂无实验性功能
},
{
name: "run",
desc: "程序运行",
default: true,
type: "bool",
temp: true,
judge: () => false,
},
{
name: "debug",
type: "bool",
desc: "调试模式",
default: false,
},
{
name: "fids",
type: "other",
desc: "设置监听板块FID",
tips: "请输入板块FID,用半角逗号 (,) 隔开",
default: "110,266,431,1566"
},
{
name: "silent",
type: "bool",
desc: "静音发送提醒",
default: false,
},
{
name: "backtime",
type: "uint",
desc: "设置轮询时间间隔",
default: 5e3 | 0,
},
{
name: "maxreply",
type: "int",
desc: "设置最多回复数 (设置非正数禁用)",
default: -1,
},
{
name: "renotify",
type: "bool",
desc: "同一个帖子重新提醒",
default: true,
},
{
name: "jmp_to_lastpost",
type: "bool",
desc: "跳转到最新回复",
default: true,
},
{
name: "root_page",
type: "other",
desc: "手动设置运行页面",
tips: "请输入运行页面,以/开头,比如运行在坛规帖页面就是:/thread-7808-1-1.html",
default: "/thread-7808-1-1.html",
callback: ((_n, _ov, nv) => (location.pathname === nv || ov === location.pathname) ? location.reload() : 0),
judge: (name, val) => !!(typeof val === "string"),
},
{
name: "root_only",
type: "bool",
desc: "仅运行在指定页面",
default: false,
callback: location.reload,
},
{
name: "banned",
type: "other",
desc: "设置不推送的用户",
default: "",
tips: "设置屏蔽的用户\n 可以设置成自己,这样就不会收到重复推送"
},
{
name: "title_filter",
type: "other",
desc: "设置正则屏蔽标题",
tips: "设置正则表达式,若命中表达式则不推送",
default: null,
},
].forEach(register);
if (config.root_only) GM_registerMenuCommand("打开运行页", () => GM_openInTab(config.root_page));
const _Prefix = "GNP-";
const storage = (config.root_only ? sessionStorage : localStorage);
const getItem = id => storage.getItem(_Prefix + id);
const setItem = (id, val) => storage.setItem(_Prefix + id, val);
const clear = () => {
const arr = [];
for (let i = 0; i < storage.length; ++i) {
const k = storage.key(i);
if (k.startsWith(_Prefix)) arr.push(k);
}
arr.forEach(k => storage.removeItem(k));
}
// core fn
async function getposts(fid) {
if (!navigator.onLine) return null;
let result = await (async () => {
const uri = "https://www.mcbbs.net/forum.php?mod=ajax&action=forumchecknew&inajax=yes&inajax=1&ajaxtarget=forumnew&uncheck=2&" +
`fid=${fid}&time=${Math.round(new Date().getTime() / 1000) - Math.round(Math.random()*100)%50 - 25}`;
let r;
try {const s = await fetch(uri); r = await s.text();}
catch(e) {console.error(e); return null;}
return parse_result(r);
})();
if (!result) return;
if (config.maxreply >= 0)
result = result.filter(item => item.reply_num <= config.maxreply);
const ban = config.banned;
result = result.filter(item => {
const rply = getItem(item.tid.toString()) ?? 0;
const rn = item.reply_num;
if (rply < rn) {
setItem(item.tid, rn);
return ban != item.lastposter;
} return false;
});
return result;
}
function trans_params(paras) {
const rt = new URLSearchParams();
for (const x in paras) rt.append(x, paras[x]);
return rt;
}
function pusher(item, fid) {
if (item.title.match(config.title_filter)) return;
const fresh = (item.reply_num == 0) | 0;
const body = ["最后回复时间", "发布时间"][fresh]
+':'+ item.lastpost_time +'\n'
+ ["回复者", "发布者"][fresh] +':'+ item.lastposter
+ "\n【来自" + fid + "号版块】";
item.title = (fresh ? "【新帖】": "【有"+ item.reply_num + "条回复】") + item.title;
notify({
text: body,
title: item.title,
image: item.icon,
tag: _Prefix + item.tid,
url: item.uri,
silent: config.silent,
});
// push(item.uri, subj, item.title, item.icon, item.tid);
}
// main function
let need_clean = false;
(async () => {
if (config.root_only && location.pathname !== config.root_page) return;
const delay = n => new Promise(r => setTimeout(r, n));
for (;;) {
if (!config.fids) return;
const arr = config.fids.split(",");
for (const fid of arr) {
if (need_clean) clear(), need_clean = false;
(await getposts(fid))?.forEach(it => pusher(it, fid));
await delay(config.backtime);
}
}
})();
GM_registerMenuCommand("清除缓存(可能导致重复推送)", () => need_clean = true);