针对 Z-Blog 官方论坛的辅助脚本
- // ==UserScript==
- // @name 「Z-Blog」论坛辅助
- // @namespace https://www.wdssmq.com/
- // @version 1.0.5
- // @author 沉冰浮水
- // @description 针对 Z-Blog 官方论坛的辅助脚本
- // @license MIT
- // @link https://greasyfork.org/zh-CN/scripts/419517
- // @null ----------------------------
- // @contributionURL https://github.com/wdssmq#%E4%BA%8C%E7%BB%B4%E7%A0%81
- // @contributionAmount 5.93
- // @null ----------------------------
- // @link https://github.com/wdssmq/userscript
- // @link https://afdian.net/@wdssmq
- // @link https://greasyfork.org/zh-CN/users/6865-wdssmq
- // @null ----------------------------
- // @noframes
- // @run-at document-end
- // @match https://bbs.zblogcn.com/*
- // @match https://app.zblogcn.com/zb_system/admin/edit.php*
- // @grant GM_xmlhttpRequest
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_addStyle
- // @require https://cdn.bootcdn.net/ajax/libs/lz-string/1.4.4/lz-string.min.js
- // @require https://cdn.bootcdn.net/ajax/libs/js-yaml/4.1.0/js-yaml.min.js
- // @require https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js
- // ==/UserScript==
- /* eslint-disable */
- /* jshint esversion: 6 */
- (function () {
- 'use strict';
- const gm_name = "zbp-xiuno";
- // 初始变量
- const $n = (selector, context = document) => context.querySelector(selector);
- const $ = window.jQuery || unsafeWindow.jQuery;
- const UM = window.UM || unsafeWindow.UM;
- const UE = window.UE || unsafeWindow.UE;
- const curHref = location.href.replace(location.hash, "");
- // localStorage 封装
- const lsObj = {
- setItem: function(key, value) {
- localStorage.setItem(key, JSON.stringify(value));
- },
- getItem: function(key, def = "") {
- const item = localStorage.getItem(key);
- if (item) {
- return JSON.parse(item);
- }
- return def;
- },
- };
- // 预置函数
- const _log = (...args) => console.log(`[${gm_name}]\n`, ...args);
- const _hash = () => location.hash.replace("#", "");
- // Get 封装
- function fnGetRequest(strURL, strData, fnCallback) {
- if (typeof strData === "function") {
- fnCallback = strData;
- strData = "";
- }
- GM_xmlhttpRequest({
- method: "GET",
- data: strData,
- url: strURL,
- onload: function(responseDetail) {
- if (responseDetail.status === 200) {
- fnCallback(responseDetail.responseText, strURL);
- } else {
- console.log(responseDetail);
- alert("请求失败,请检查网络!");
- }
- },
- });
- }
- // formtTime 封装
- function fnFormatTime() {
- const objTime = new Date();
- const strYear = objTime.getFullYear();
- const strMonth = objTime.getMonth() + 1;
- const strDate = objTime.getDate();
- objTime.getHours();
- objTime.getMinutes();
- objTime.getSeconds();
- return (
- [strYear, strMonth, strDate].map(n => n.toString().padStart(2, "0")).join("-") +
- // " " +
- // [strHour, strMinute, strSecond].map((n) => n.toString().padStart(2, "0")).join(":") +
- ""
- ).trim();
- }
- (() => {
- const $body = $n("body");
- const defData = {
- status: 0, // 用于记录状态
- href: curHref,
- };
- // 更新 lsData 中的 status 状态
- const updateStatus = (status) => {
- const lsData = lsObj.getItem("xiuno_login", defData);
- lsData.status = status;
- lsObj.setItem("xiuno_login", lsData);
- };
- // 登录后跳转前登录前的页面
- const goUrl = () => {
- // 读取 localStorage
- const lsData = lsObj.getItem("xiuno_login", defData);
- // 根据记录的状态判断是否跳转
- if (lsData.status === 1) {
- // 更新状态
- updateStatus(2);
- // 跳转前的页面地址
- location.href = lsData.href;
- }
- };
- // 主入口函数,判断是否登录
- const checkLogin = () => {
- // 判断 body 内容是否为空
- if ($body.textContent.trim() !== "") {
- // 有内容,说明已登录,根据记录的地址跳转
- goUrl();
- return;
- }
- // 更新状态及 href 到 localStorage
- updateStatus(1);
- // 跳转登录页
- location.href = "/user-login.html";
- };
- // 延时 3 秒检查
- setTimeout(checkLogin, 3000);
- })();
- /* globals LZString jsyaml*/
- (() => {
- // 定义按钮及提示信息
- const $btnBad = $(" <a class=\"btn btn-primary\">BAD</a>");
- const strTip = "<p>此贴内容或签名不符合论坛规范已作屏蔽处理,请查看置顶贴,以下为原始内容备份。</p>";
- // 绑定点击事件
- $btnBad.css({ color: "#fff" }).click(function () {
- let um = UM.getEditor("message");
- let str = um.getContent();
- if (str.indexOf("#~~") > -1) {
- return;
- }
- let strCode = LZString.compressToBase64(str);
- um.setContent(strTip + `<p>#~~${strCode}~~#</p>`);
- console.log(LZString.decompressFromBase64(strCode));
- // let strDeCode = LZString.decompressFromBase64(strCode);
- // um.setContent(strCode + strDeCode);
- });
- // 放置按钮
- if ($("input[name=update_reason]").length > 0) {
- $("#submit").after($btnBad);
- }
- // 解码
- $("div.message").each(function () {
- let $secP = $(this).find("p:nth-child(2)");
- if ($secP.length == 0) {
- console.log("skip");
- return;
- }
- let str = $secP.html();
- if (str.indexOf("#~~") == -1) {
- return;
- }
- console.log(str);
- str = str.replace(/#~~(.+)~~#/, function (a, b) {
- console.log(arguments);
- let strDeCode = LZString.decompressFromBase64(b);
- console.log(strDeCode);
- return strDeCode;
- });
- $secP.after(str).remove();
- });
- })();
- // _pid.js | 楼层地址
- (() => {
- $("li.media.post").each(function () {
- const $me = $(this);
- const pid = $me.data("pid");
- const $date = $me.find("span.date");
- $date.after(
- `<a class="text-grey ml-2" title="获取当前楼层链接" href="${curHref}#${pid}">「楼层地址」</a>`,
- );
- });
- })();
- /* globals jsyaml*/
- (() => {
- // _log(curHref);
- if (curHref.indexOf("bbs.zblogcn.com") === -1) {
- return;
- }
- _log("devView");
- // CDN 地址替换
- function fnGetCDNUrl(url) {
- const arrMap = [
- ["https://github.com/", "https://cdn.jsdelivr.net/gh/"],
- ["/blob/", "@"],
- ];
- let cdnUrl = url;
- arrMap.forEach((line) => {
- cdnUrl = cdnUrl.replace(line[0], line[1]);
- });
- return cdnUrl;
- }
- // time 2 hour
- function fnTime2Hour(time = null) {
- if (!time) {
- time = new Date();
- }
- // 时间戳
- const timeStamp = time.getTime();
- return Math.floor(timeStamp / 1000 / 60 / 60);
- }
- // 默认配置项
- const defConfig = {
- useCDN: false,
- ymlList: [
- "2023H1",
- "2022H2",
- "2022H1",
- "2021H2",
- ],
- ver: "2023-04-24",
- isNew: true,
- };
- // 配置项读取和首次保存
- const curConfig = GM_getValue("_devConfig", defConfig);
- if (curConfig.isNew || curConfig.ver !== defConfig.ver) {
- curConfig.isNew = false;
- GM_setValue("_devConfig", defConfig);
- }
- // 初始化 ymlList
- function fnInitYML() {
- const useCDN = curConfig.useCDN;
- let ymlList = curConfig.ymlList;
- ymlList = ymlList.map((yml) => {
- let url = `https://raw.githubusercontent.com/wdssmq/ReviewLog/main/data/${yml}.yml`;
- if (useCDN) {
- url = fnGetCDNUrl(url);
- }
- return url;
- });
- return ymlList;
- }
- // 模板函数
- function fnStrtr(
- str,
- obj,
- callback = (str) => {
- return str;
- },
- ) {
- let rltStr = str;
- for (const key in obj) {
- if (Object.hasOwnProperty.call(obj, key)) {
- const value = obj[key];
- const reg = new RegExp(`#${key}#`, "g");
- rltStr = rltStr.replace(reg, value);
- }
- }
- return callback(rltStr);
- }
- // 数据读取封装
- const gobDev = {
- data: {
- lstLogs: [],
- lstCheck: null,
- },
- init: function () {
- this.data = lsObj.getItem("gobDev", this.data);
- _log("gobDev init", this.data);
- this.ymlList = fnInitYML();
- },
- checkUrl: function (url) {
- let rlt = null;
- this.data.lstLogs.forEach((log) => {
- if (log.url.indexOf(url) > -1) {
- _log("checkUrl", url, log.url);
- rlt = log;
- }
- });
- return rlt;
- },
- clear: function () {
- this.data.lstLogs = [];
- lsObj.setItem("gobDev", this.data);
- },
- save: function () {
- lsObj.setItem("gobDev", this.data);
- },
- update: function () {
- const curHour = fnTime2Hour();
- if (this.data.lstCheck === curHour && this.data.lstLogs.length > 0) {
- return;
- }
- this.data.lstLogs = [];
- this.data.lstCheck = curHour;
- this.ajax();
- },
- ajax: function () {
- const self = this;
- this.ymlList.forEach((yml) => {
- fnGetRequest(yml, (responseText, url) => {
- _log("ajax", url);
- const ymlObj = jsyaml.load(responseText, "utf8");
- const curLogs = self.data.lstLogs;
- self.data.lstLogs = curLogs.concat(ymlObj);
- self.save();
- });
- });
- },
- };
- gobDev.init();
- gobDev.update();
- // 缓存清理封装
- const _clearAct = (doClear = false) => {
- const curHash = _hash();
- if (curHash === "clearDone") {
- window.location.href = `${curHref}`;
- // window.location.reload();
- } else if (doClear || curHash === "clear") {
- gobDev.clear();
- window.location.href = `${curHref}#clearDone`;
- setTimeout(() => {
- window.location.reload();
- }, 1000);
- }
- };
- // 默认调用一次用于清后的跳转
- _clearAct();
- // 缓存清理按钮
- const $btnClear = $("<span class=\"small\"><a href=\"javascript:;\" title=\"清理缓存\" class=\"badge badge-warning\">清理缓存</a></span>");
- $btnClear.on("click", function () {
- if (confirm("清理缓存?")) {
- _clearAct(1);
- }
- });
- // 根据 log 数据设置状态徽章
- const _setBadge = (log, $item = null, act = "after") => {
- // console.log("log", log);
- let badgeClass, $badge;
- const status = log?.status || "未记录";
- switch (status) {
- case "通过":
- badgeClass = "badge-success";
- break;
- case "进行中":
- badgeClass = "badge-info";
- break;
- case "拒绝":
- badgeClass = "badge-danger";
- break;
- default:
- badgeClass = "badge-warning";
- break;
- }
- $badge = $(`<span class="badge ${badgeClass}">${status}</span>`);
- if (act === "after") {
- $item.after($badge);
- // $item.after($btnClear);
- } else {
- $item.append($badge);
- $item.append(" ");
- $item.append($btnClear);
- }
- };
- // 标题列表
- const $titleList = $("li.media .subject a");
- $titleList.each(function () {
- const $this = $(this);
- const href = $this.attr("href");
- const title = $this.text();
- if (title.indexOf("申请开发者") === -1) {
- return;
- }
- const log = gobDev.checkUrl(href);
- _setBadge(log, $this);
- });
- // 博文内页
- const $h4 = $(".media-body h4");
- let title = $h4.text().trim();
- if (title.indexOf("申请开发者") === -1) {
- return;
- }
- const log = gobDev.checkUrl(curHref);
- _setBadge(log, $h4, "append");
- _log("curLog", log);
- // 初始化
- $("div.message").each(function () {
- if ($(this).attr("isfirst") == 1) {
- $(this).prepend(
- "<blockquote class=\"blockquote\"><pre class=\"pre-yml\"></pre></blockquote>",
- );
- $(".pre-yml").text("标题格式错误");
- }
- });
- // 标题内容解析
- title = title.replace(/\[|【/g, "「").replace(/\]|】/g, "」");
- const objMatch = title.match(/「([^」]+)」「(theme|plugin)」/);
- _log("objMatch", objMatch);
- if (!objMatch) {
- return;
- }
- // YML 模板
- const tplYML = `
- - id: #id#
- type: #type#
- status: #status#
- rating: #rating#
- url: #url#
- git: #git#
- date:
- - #date#
- reviewers:
- - #reviewers#
- `;
- // 构建 YML
- const styYML = fnStrtr(
- tplYML,
- {
- id: objMatch[1],
- type: objMatch[2],
- status: log ? log.status : "进行中",
- rating: log ? log.rating : "",
- url: curHref,
- git: log ? log.git : "",
- date: log ? log.date[0] : fnFormatTime(),
- reviewers: log ? log.reviewers.join("\n_4_- ") : "null",
- },
- (str) => {
- str = str.replace(/\n/g, "\\|");
- // str = str.replace(/\s{6}/g, "_2__2_");
- // str = str.replace(/\s{4}/g, "_2_");
- str = str.replace(/_4_/g, "_2__2_");
- str = str.replace(/_2_/g, " ");
- str = str.replace(/\\\|/g, "\n");
- const objMatch = title.match(/(通过|拒绝)/);
- if (objMatch) {
- str = str.replace(/status: 进行中/, `status: ${objMatch[1]}`);
- }
- return str;
- },
- );
- // 插入 YML
- $(".pre-yml").text(`${styYML}`);
- })();
- // 引入元素插入
- (() => {
- if (typeof UM === "undefined") {
- return;
- }
- // 引用标签插入封装
- function fnBlockQuote() {
- const umObj = UM.getEditor("message");
- if (!umObj.isFocus()) {
- umObj.focus(true);
- }
- const addHTML = "<blockquote class=\"blockquote\"><p><br></p></blockquote><p><br></p>";
- // umObj.execCommand("insertHtml", addHTML);
- umObj.setContent(addHTML, true);
- }
- // 添加引用按钮
- $("head").append("<style>.edui-icon-blockquote:before{content:\"\\f10d\";}");
- (() => {
- const $btn = $.eduibutton({
- icon: "blockquote",
- click: function () {
- fnBlockQuote();
- },
- title: UM.getEditor("message").getLang("labelMap")["blockquote"] || "",
- });
- $(".edui-btn-name-insertcode").after($btn);
- })();
- // 自动排版函数封装
- function fnAutoFormat() {
- const umObj = UM.getEditor("message");
- let strHTML = umObj.getContent();
- strHTML = strHTML.replace(
- /<blockquote>/g,
- "<blockquote class=\"blockquote\">",
- );
- // 第二个参数为 true 表示追加;
- umObj.setContent(strHTML, false);
- }
- // 添加自动排版按钮
- $("head").append("<style>.edui-btn-auto-format:before{content:\"fix\";}");
- (() => {
- const $btn = $.eduibutton({
- icon: "auto-format",
- click: function () {
- fnAutoFormat();
- },
- title: "自动排版",
- });
- $(".edui-btn-name-insertcode").after($btn);
- })();
- })();
- /* global showdown */
- class GM_editor {
- $def;
- defEditor = null;
- htmlContent = "";
- $md;
- mdEditor = null;
- mdContent = "";
- defOption = {
- init($md) { },
- autoSync: false,
- curType: "html",
- };
- option = {};
- constructor(option) {
- this.option = Object.assign({}, this.defOption, option);
- this.init();
- this.option.init(this.$md);
- this.getContent("html").covert2("md").syncContent("md");
- }
- init() {
- const _this = this;
- this.$def = this.option.$defContainer || $(".edui-container");
- this.$md = this.createMdEditor();
- // 编辑器操作对象
- this.defEditor = this.option.defEditor || UM.getEditor("message");
- this.mdEditor = {
- // 内容变化时触发
- addListener(type, fn) {
- if (type === "contentChange") {
- // _log(_this.$md);
- _this.$md.find("#message_md").on("input", fn);
- }
- },
- // 获取内容
- getContent() {
- return _this.$md.find("#message_md").val();
- },
- // 写入内容
- setContent(content) {
- _this.$md.find("#message_md").text(content);
- },
- };
- if (this.option.autoSync) {
- this.defEditor.addListener("contentChange", () => {
- if (_this.option.curType === "md") {
- return;
- }
- this.getContent("html").covert2("md").syncContent("md");
- });
- this.mdEditor.addListener("contentChange", () => {
- if (_this.option.curType === "html") {
- return;
- }
- this.getContent("md").covert2("html").syncContent("html");
- });
- }
- }
- // 读取内容
- getContent(type = "html") {
- if (type === "html") {
- this.htmlContent = this.defEditor.getContent();
- } else if (type === "md") {
- this.mdContent = this.mdEditor.getContent();
- }
- return this;
- }
- // 封装转换函数
- covert2(to = "md") {
- const converter = new showdown.Converter();
- if (to === "md") {
- this.mdContent = converter.makeMarkdown(this.htmlContent);
- } else if (to === "html") {
- this.htmlContent = converter.makeHtml(this.mdContent);
- }
- return this;
- }
- // 封装同步函数
- syncContent(to = "md") {
- if (to === "md") {
- this.mdEditor.setContent(this.mdContent);
- } else if (to === "html") {
- this.defEditor.setContent(this.htmlContent, false);
- }
- }
- // 自动设置 #message_md 的高度
- autoSetHeight() {
- const $mdText = this.$md.find("#message_md");
- // 自动设置高度
- $mdText.height(0);
- $mdText.height($mdText[0].scrollHeight + 4);
- // 判断并绑定 input 事件
- if ($mdText.data("bindInput")) {
- return;
- }
- $mdText.data("bindInput", true);
- $mdText.on("input", () => {
- this.autoSetHeight();
- });
- }
- // 切换编辑器
- switchEditor() {
- this.$def.toggle();
- this.$md.toggle();
- // 根据结果设置 curType
- this.option.curType = this.$def.css("display") === "none" ? "md" : "html";
- // 切换后自动设置高度
- this.autoSetHeight();
- }
- // 创建 markdown 编辑器
- createMdEditor() {
- return $(`
- <div class="mdui-container" style="display: none;">
- <div class="mdui-body">
- <textarea id="message_md" name="message_md" placeholder="markdown" class="mdui-text"></textarea>
- </div>
- </div>
- `);
- }
- }
- GM_addStyle(`
- .is-pulled-right {
- float: right;
- }
- .mdui-container {
- border: 1px solid #d4d4d4;
- padding: 5px 10px;
- }
- .mdui-container:focus-within {
- border: 1px solid #4caf50;
- }
- .mdui-text {
- border: none;
- width: 100%;
- min-height: 300px;
- height: auto;
- }
- .mdui-text:focus,
- .mdui-text:focus-visible {
- outline: none;
- box-shadow: none;
- }
- `);
- const mainForBBS = () => {
- const gm_editor = new GM_editor({
- init($md) {
- $(".edui-container").after($md);
- },
- autoSync: true,
- });
- const btnSwitchEditor = `
- <button class="btn btn-primary" type="button" id="btnSwitchEditor">切换编辑器</button>
- `;
- // 判断是否有 name 为 fid 的 select
- if ($("select[name='fid']").length === 0) {
- // quotepid 后追加一行 .form-group
- $("input[name='quotepid']").after("<div class=\"form-group\"><span></span></div>");
- }
- // name 为 quotepid 的 input 下一行追加切换按钮
- $("input[name='quotepid'] + .form-group").addClass("d-flex justify-content-between").append(btnSwitchEditor);
- // 切换编辑器
- $("#btnSwitchEditor").click(() => {
- gm_editor.switchEditor();
- });
- };
- const mainForAPP = () => {
- const gm_editor = new GM_editor({
- init($md) {
- $("#editor_content").after($md);
- // const $mdText = $md.find("#message_md");
- // $mdText.height($("#editor_content").height() - 10);
- $(".mdui-body").css({
- paddingTop: ".3em",
- });
- },
- $defContainer: $("#editor_content"),
- defEditor: UE.getEditor("editor_content"),
- autoSync: true,
- });
- // # cheader 元素内部追加切换按钮
- $("#cheader").append(`
- <span class="is-pulled-right">「<a href="javascript:;" class="btn btn-primary" id="btnSwitchEditor" title="切换编辑器">切换编辑器</a>」</span>
- `);
- // 切换编辑器
- $("#btnSwitchEditor").click(() => {
- gm_editor.switchEditor();
- });
- };
- (() => {
- // 判断是否在应用中心编辑页
- if (curHref.indexOf("edit.php") > -1) {
- // _log(UE)
- const editor_api = window.editor_api || unsafeWindow.editor_api;
- editor_api.editor.content.obj.ready(mainForAPP);
- }
- // 判断是否在论坛发帖、回帖页
- if ($("textarea#message").length > 0 && $("li.newpost").length === 0) {
- mainForBBS();
- }
- })();
- })();