// ==UserScript==
// @name MCBBS Extender Core
// @namespace https://i.zapic.cc
// @version v2.0.3
// @description MCBBS模块化优化框架
// @author Zapic
// @match https://*.mcbbs.net/*
// @run-at document-body
// ==/UserScript==
//Core
const MExt_version = "2.0.3";
const MExt_vercode = "121043";
(() => {
//夹带私货
console.log(" %c Zapic's Homepage %c https://i.zapic.cc ", "color: #ffffff; background: #E91E63; padding:5px;", "background: #000; padding:5px; color:#ffffff");
// jQuery检查
if (typeof jQuery == "undefined") {
console.error("This page does NOT contain JQuery,MCBBS Extender will not work.");
return;
}
//在手机页面主动禁用
if (document.getElementsByTagName('meta').viewport) {
console.log("MCBBS Extender not fully compatible with Moblie page,exit manually");
return;
}
const selfMd = {
"meta": {
"id": "MExt_Core",
"name": "MCBBS Extender Core Loader",
"version": "2.0.3",
"updateInfo":[]
}
}
// 初始化配置
let valueList = null;
const configList = [];
const moduleList = {};
// 加载ValueStorage
try {
valueList = JSON.parse(localStorage.getItem("MExt_config"));
if (typeof valueList != "object" || valueList == null) {
valueList = {};
localStorage.setItem("MExt_config", "{}")
}
} catch (ig) {
valueList = {};
localStorage.setItem("MExt_config", "{}")
}
// 导出模块
const exportModule = (...modules) => {
for (let m of modules) {
try {
moduleLoader(m);
dispatchEvent(new CustomEvent("MExtModuleLoaded",{"detail":m.meta}));
} catch (e) {
console.error("Error occurred while try to load a module:\n" + e);
}
}
}
const dlg = (m) => {
console.debug("[MCBBS Extender]" + m);
};
const setValue = (name, val) => {
valueList[name] = val;
localStorage.setItem("MExt_config", JSON.stringify(valueList));
}
const getValue = (name) => {
return valueList[name];
}
const deleteValue = (name) => {
delete valueList[name];
localStorage.setItem("MExt_config", JSON.stringify(valueList));
}
const appendStyle = (style) => {
let s = document.createElement("style");
s.className = "MExtStyle";
s.innerHTML = style;
document.head.appendChild(s);
};
const getRequest = (variable, url = "") => {
let query = url ? /\?(.*)/.exec(url)[1] : window.location.search.substring(1);
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return (false);
}
// 模块加载器
const moduleLoader = (module) => {
// 载入配置项
if (typeof module.meta == "undefined" || typeof module.meta.id !== "string") {
throw new Error("Invalid module meta");
}
moduleList[module.meta.id] = module.meta;
if (typeof module.config !== "undefined") {
module.config.forEach((v) => {
if (typeof getValue(v.id) == "undefined") {
setValue(v.id, v.default);
}
let config = v;
v.value = getValue(v.id);
configList.push(config);
});
}
// 判断是否应该运行
if (typeof module.case == "function") {
if (!module.case()) {
return;
}
}
// 加载模块CSS
if (typeof module.style == 'string') {
appendStyle(module.style);
}
// 运行模块Core
if (typeof module.core == "function") {
module.core();
}
}
// 对外暴露API
const MExt = {
"exportModule": exportModule,
"jQuery": unsafeWindow.jQuery,
"configList": configList,
"moduleList": moduleList,
"versionName": MExt_version,
"versionCode": MExt_vercode,
"Storage": {
"get": getValue,
"set": setValue,
"delete": deleteValue
},
"Units": {
"appendStyle": appendStyle,
"getRequest": getRequest,
"debugLog": dlg
}
};
unsafeWindow.MExt = MExt;
unsafeWindow.dispatchEvent(new CustomEvent("MExtLoaded",{bubbles: true}));
exportModule(selfMd);
})();
// Discuz UI Operate Event Dispatcher
(async ()=>{
await new Promise(_ => { !unsafeWindow.MExt ? unsafeWindow.addEventListener("MExtLoaded", __ => { _(unsafeWindow.MExt) }) : _(unsafeWindow.MExt)});
const removeHandler = (r) => {
switch (r.target.nodeName) {
case "TBODY":
if (typeof r.target.id != "undefined") {
if (r.target.id.lastIndexOf("normalthread_") >= 0) {
r.target.dispatchEvent(new CustomEvent("ThreadPreviewClosed",{bubbles: true}));
}
}
break;
case "DIV":
if (typeof r.target.id != 'undefined' && r.target.id.lastIndexOf("threadPreview_") >= 0) {
if (r.removedNodes[0].nodeName == "SPAN" && r.removedNodes[0].innerText == " 请稍候...") {
r.target.dispatchEvent(new CustomEvent("ThreadPreviewOpened",{bubbles: true}));
}
} else if (r.removedNodes.length >= 3 && r.target.id.lastIndexOf("post_") >= 0) {
if (r.removedNodes[0].nodeName == "A" && r.removedNodes[0].name == "newpost" && r.removedNodes[0].parentNode != null) {
r.target.dispatchEvent(new CustomEvent("ThreadFlushStarted",{bubbles: true}));
}
} else if (r.target.id == "append_parent") {
if (r.removedNodes[0].nodeName == "DIV") {
if (r.removedNodes[0].id == "fwin_rate") {
r.target.dispatchEvent(new CustomEvent("RateWindowClosed",{bubbles: true}));
} else if (r.removedNodes[0].id == "fwin_reply") {
r.target.dispatchEvent(new CustomEvent("ReplyWindowClosed",{bubbles: true}));
} else if (typeof r.removedNodes[0].id != 'undefined' && r.removedNodes[0].id.lastIndexOf("fwin_miscreport") >= 0) {
r.target.dispatchEvent(new CustomEvent("ReportWindowClosed",{bubbles: true}));
}
}
}
break;
}
}
const addHandler = (r) => {
switch (r.target.nodeName) {
case "DIV":
if (typeof r.target.id != "undefined") {
if (r.target.id.lastIndexOf("threadPreview_") >= 0) {
if (r.addedNodes[0].nodeName == "SPAN" && r.addedNodes[0].innerText == " 请稍候...") {
r.target.dispatchEvent(new CustomEvent("ThreadPreviewPreOpen",{bubbles: true}));
}
} else if (r.addedNodes.length >= 3 && r.target.id.lastIndexOf("post_") >= 0) {
if (r.addedNodes[0].nodeName == "A" && r.addedNodes[0].name == "newpost" && r.addedNodes[0].parentNode != null) {
r.target.dispatchEvent(new CustomEvent("ThreadFlushFinished",{bubbles: true}));
}
} else if (r.target.id == "append_parent") {
if (r.addedNodes[0].nodeName == "DIV") {
if (r.addedNodes[0].id == "fwin_rate") {
r.addedNodes[0].dispatchEvent(new CustomEvent("RateWindowPreOpen",{bubbles: true}));
} else if (r.addedNodes[0].id == "fwin_reply") {
r.addedNodes[0].dispatchEvent(new CustomEvent("ReplyWindowPreOpen",{bubbles: true}));
} else if (typeof r.addedNodes[0].id != 'undefined' && r.addedNodes[0].id.lastIndexOf("fwin_miscreport") >= 0) {
r.addedNodes[0].dispatchEvent(new CustomEvent("ReportWindowPreOpen",{bubbles: true}));
}
}
} else if (r.target.id === "") {
if (r.target.parentElement != null && r.target.parentElement == "postlistreply") {
r.target.dispatchEvent(new CustomEvent("NewReplyAppended",{bubbles: true}));
}
}
}
break;
case "A":
if (r.addedNodes[0].nodeName == "#text" && typeof tid == "undefined") {
if (r.addedNodes[0].nodeValue == "正在加载, 请稍后...") {
r.target.dispatchEvent(new CustomEvent("ThreadsListLoadStart",{bubbles: true}));
} else if (r.addedNodes[0].nodeValue == "下一页 »") {
r.target.dispatchEvent(new CustomEvent("ThreadsListLoadFinished",{bubbles: true}));
}
}
break;
case "TD":
if (r.target.id == "fwin_content_rate" && r.addedNodes[0].nodeName == "DIV" && r.addedNodes[0].id == "floatlayout_topicadmin") {
r.target.dispatchEvent(new CustomEvent("RateWindowOpened",{bubbles: true}));
}
if (r.target.id == "fwin_content_reply" && r.addedNodes[0].nodeName == "H3" && r.addedNodes[0].id == "fctrl_reply") {
r.target.dispatchEvent(new CustomEvent("ReplyWindowOpened",{bubbles: true}));
}
if (typeof r.target.id != 'undefined' && r.target.id.lastIndexOf("fwin_content_miscreport") >= 0 && r.addedNodes[0].nodeName == "H3" && r.addedNodes[0].id.lastIndexOf("fctrl_miscreport") >= 0) {
r.target.dispatchEvent(new CustomEvent("ReportWindowOpened",{bubbles: true}));
}
break;
}
}
const mainHandler = (r) => {
if (r.type == "childList") {
if (r.addedNodes.length > 0) {
addHandler(r);
}
if (r.removedNodes.length > 0) {
removeHandler(r);
}
}
}
let O = new MutationObserver((e) => {
for (let record of e) {
mainHandler(record);
}
});
document.addEventListener("DOMContentLoaded",()=>{
O.observe(document.body, { childList: true, subtree: true });
});
// 钩住DiscuzAjax函数,使其触发全局事件
const __ajaxpost = unsafeWindow.ajaxpost;
unsafeWindow.ajaxpost = (formid, showid, waitid, showidclass, submitbtn, recall) => {
let relfunc = () => {
if (typeof recall == 'function') {
recall();
} else {
eval(recall);
}
this.dispatchEvent(new CustomEvent("DiscuzAjaxPostFinished",{bubbles: true}));
}
__ajaxpost(formid, showid, waitid, showidclass, submitbtn, relfunc);
}
const __ajaxget = unsafeWindow.ajaxget;
unsafeWindow.ajaxget = (url, showid, waitid, loading, display, recall) => {
let relfunc = () => {
if (typeof recall == 'function') {
recall();
} else {
eval(recall);
}
this.dispatchEvent(new CustomEvent("DiscuzAjaxGetFinished",{bubbles: true}));
}
__ajaxget(url, showid, waitid, loading, display, relfunc);
}
})();
// Config Panel
(async () => {
const MExt = await new Promise(_ => { !unsafeWindow.MExt ? unsafeWindow.addEventListener("MExtLoaded", __ => { _(unsafeWindow.MExt) }) : _(unsafeWindow.MExt)});
const $ = MExt.jQuery;
const Md = {
"meta": {
"id": "MExt_Config",
"name": "MCBBS Extender 设置",
"version": "2.0.3",
"updateInfo": []
},
"style": `.conf_contain {
max-height: 45vh;
overflow-y: auto;
padding-right: 5px;
overflow-x: hidden;
scrollbar-color: rgba(0, 0, 0, 0.17) #f7f7f7;
scrollbar-width: thin;
}
.alert_info ::-webkit-scrollbar {
background: #f7f7f7;
height: 7px;
width: 7px
}
.alert_info ::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.35);
}
.alert_info ::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.17);
}
.conf_item {
line-height: 1.2;
margin-bottom: 5px;
}
.conf_title {
font-weight: 1000;
}
.conf_subtitle {
font-size: 10px;
color: rgba(0, 0, 0, 0.5);
padding-right: 40px;
display: block;
}
.conf_check {
float: right;
margin-top: -25px;
}
.conf_input {
float: right;
width: 30px;
margin-top: -27px;
}
.conf_longinput {
width: 100%;
margin-top: 5px;
}
.conf_textarea {
width: calc(100% - 4px);
margin-top: 5px;
resize: vertical;
min-height: 50px;
}`
};
MExt.exportModule(Md);
const getRequest = MExt.Units.getRequest;
$(() => {
// 发送警告
if (location.pathname == "/forum.php" && getRequest('mod') == "post" && getRequest('action') == "newthread" && getRequest('fid') == "246") {
const alertWin = document.createElement("div");
alertWin.style = "max-width:430px;position: fixed; left: 20px; top: 80px; z-index: 9999; transform: matrix3d(1, 0, 0, 0.0001, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1.025) translateX(-120%); background: rgba(228, 0, 0, 0.81); color: white; padding: 15px; transition-duration: 0.3s; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.66) 2px 2px 5px 0px;";
alertWin.innerHTML = `<h1 style="font-size: 3em;float: left;margin-right: 12px;font-weight: 500;margin-top: 6px;">警告</h1><span style="font-size: 1.7em;">您正在向反馈与投诉版发表新的帖子</span><br>如果您正在向论坛报告论坛内的Bug,请先关闭此脚本再进行一次复现,以确保Bug不是由MCBBS Extender造成的.`;
document.body.appendChild(alertWin);
setTimeout(() => { alertWin.style.transform = "matrix3d(1, 0, 0, 0.0001, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1.025)"; }, 10);
setTimeout(() => { alertWin.style.transform = "none"; }, 300);
setTimeout(() => { alertWin.style.transform = "translateX(-120%)"; }, 10000);
}
// 设置界面初始化
const btnContainer = document.createElement("li");
const btnMExt = document.createElement("a");
btnMExt.href = "javascript: void(0);";
btnMExt.id = "MExt_config";
btnMExt.innerHTML = "MCBBS Extender 设置";
btnContainer.appendChild(btnMExt);
const target = document.querySelector("#user_info_menu .user_info_menu_btn");
if(target == null) return;
target.appendChild(btnContainer);
btnMExt.addEventListener("click", () => {
let confwinContent = '<style>body{overflow:hidden}.altw{width:700px;max-width:95vh;}.alert_info {background-image: unset;padding-left: 20px;padding-right: 17px;}</style><div class="conf_contain">';
const inputType = {
"check": '',
"num": '',
"text": '',
"textarea": ''
};
MExt.configList.forEach((v) => {
switch (v.type) {
case "check":
inputType.check += '<p class="conf_item"><span class="conf_title">' + v.name + '</span><br><span class="conf_subtitle">' + v.desc + '</span><input class="conf_check" type="checkbox" id="in_' + v.id + '"></input></p>';
break;
case "num":
inputType.num += '<p class="conf_item"><span class="conf_title">' + v.name + '</span><br><span class="conf_subtitle">' + v.desc + '</span><input type="number" class="conf_input" id="in_' + v.id + '"></input></p>';
break;
case "text":
inputType.text += '<p class="conf_item"><span class="conf_title">' + v.name + '</span><br><span class="conf_subtitle">' + v.desc + '</span><input type="text" class="conf_longinput" id="in_' + v.id + '"></input></p>';
break;
case "textarea":
inputType.textarea += '<p class="conf_item"><span class="conf_title">' + v.name + '</span><br><span class="conf_subtitle">' + v.desc + '</span><textarea class="conf_textarea" id="in_' + v.id + '"></textarea></p>';
break;
default:
inputType.check += '<p class="conf_item"><span class="conf_title">' + v.name + '</span><br><span class="conf_subtitle">' + v.desc + '</span><input class="conf_check" type="checkbox" id="in_' + v.id + '"></input></p>';
break;
}
});
confwinContent += inputType.check + inputType.num + inputType.text + inputType.textarea + '</div>';
unsafeWindow.showDialog(
confwinContent,
"confirm",
"MCBBS Extender 设置",
() => {
MExt.configList.forEach((v) => {
let val = '';
if (v.type == "num" || v.type == "text" || v.type == "textarea") {
val = $("#in_" + v.id).val();
} else {
val = $("#in_" + v.id).prop("checked");
}
MExt.ValueStorage.set(v.id, val);
});
setTimeout(() => {
unsafeWindow.showDialog("设置已保存,刷新生效<style>.alert_info{background:url(https://www.mcbbs.net/template/mcbbs/image/right.gif) no-repeat 8px 8px}</style>", "confirm", "", () => { location.reload() }, true, () => { }, "", "刷新", "确定");
});
},
true,
() => { },
"MCBBS Extender " + MExt.versionName + " - <s>世界第二委屈公主殿下</s>"
);
MExt.configList.forEach((v) => {
if (v.type == "num" || v.type == "text" || v.type == "textarea") {
$("#in_" + v.id).val(MExt.ValueStorage.get(v.id));
} else {
$("#in_" + v.id).prop("checked", MExt.ValueStorage.get(v.id));
}
});
});
});
})();
// Update Manager
(async () => {
const MExt = await new Promise(_ => { !unsafeWindow.MExt ? unsafeWindow.addEventListener("MExtLoaded", __ => { _(unsafeWindow.MExt) }) : _(unsafeWindow.MExt)});
MExt.exportModule({
"meta": {
"id": "MExt_updateManager",
"name": "MCBBS Extender Update Manager",
"version": "2.0.3",
"updateInfo": []
}
});
if (localStorage.getItem("MExt_UpdateMgr") == null) {
localStorage.setItem("MExt_UpdateMgr", JSON.stringify(MExt.moduleList));
unsafeWindow.showDialog("<b>欢迎使用MCBBS Extender</b>.<br>脚本本身不包含任何功能,请到<a style=\"color: #E91E63\" href=\"https://github.com/Proj-MExt/Modules-Repo\">模块仓库</a>寻找模块.<br>设置按钮已经放进入了您的个人信息菜单里,如需调整设置请在个人信息菜单里查看.", "right", "欢迎", () => {
unsafeWindow.showMenu('user_info');
unsafeWindow.MExt.jQuery("#MExt_config").css("background-color", "#E91E63").css("color", "#fff");
setTimeout(() => {
unsafeWindow.hideMenu('user_info_menu');
unsafeWindow.MExt.jQuery("#MExt_config").css("background-color", "").css("color", "");
}, 3000);
});
return;
}
let updateContent = '';
let source = null;
try {
source = JSON.parse(localStorage.getItem("MExt_UpdateMgr"));
} catch(e){
localStorage.setItem("MExt_UpdateMgr", JSON.stringify(MExt.moduleList));
return;
}finally {
localStorage.setItem("MExt_UpdateMgr", JSON.stringify(MExt.moduleList));
}
const compareVer = (b,a) => {
return [b,a][0] != [b,a].sort()[0];
}
for (let m in MExt.moduleList ){
if(typeof source[m] != "undefined" && typeof MExt.moduleList[m].version != "undefined"){
if(compareVer(MExt.moduleList[m].version,source[m].version)){
if(typeof MExt.moduleList[m].updateInfo !="undefined" && MExt.moduleList[m].updateInfo.length > 0){
updateContent += "<b>" + (typeof MExt.moduleList[m].name == "undefinded" ? MExt.moduleList[m].id : MExt.moduleList[m].name) + "</b> " + source[m].version + " > " + MExt.moduleList[m].version + "<br>";
for(let info of MExt.moduleList[m].updateInfo){
updateContent += info + "<br>"
}
}
}
}
}
if(updateContent == "") return;
unsafeWindow.showDialog("<b>模块已更新</b>" + updateContent, "right");
})();