打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
登录后可编辑和发表评论。

MediaWiki:Gadget-deletion.js

MediaWiki界面页面

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl + F5Ctrl + R(Mac为 R
  • Google Chrome:Ctrl + Shift + R(Mac为 Shift R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl + F5
// <pre>
"use strict";
$(() => (async () => {
    if (!mw.config.get("wgIsArticle") || !mw.config.get("wgUserGroups").includes("sysop") || !$(".mw-category-generated > div")[0]) {
        return;
    }

    // await mw.loader.using(["ext.gadget.site-lib", "mediawiki.util", "mediawiki.api", "ext.gadget.libOOUIDialog"]);

    const deduplicate = (iterable) => [...new Set(iterable).values()];
    const generatePageLinkSelector = (title) => deduplicate([encodeURI(title), mw.util.wikiUrlencode(title)]).map((selector) => `a[href$="/${selector}"]`).join(",");

    let globalDeletionLock = false;
    const DELCATS = {
        "https://voca.wiki/": "即将删除的页面"
    };
    const PAGENAME = mw.config.get("wgPageName");
    const USERNAME = mw.config.get("wgUserName");
    // Make sure that all links open in a new tab when locked
    $("body").on("click", "a", (e) => globalDeletionLock ? window.open(e.target.href, "_blank") && false : null);

    const api = new mw.Api();
    const $root = $(".mw-category-generated"), $items = $root.find("li");
    const $control = $("<p id='batdel-control'>");
    const $portlet = $(mw.util.addPortletLink("p-cactions", "#", wgULS("批量删除本分类下页面", "批次刪除本分類下頁面"), "ca-batdel", wgULS("批量删除本分类下页面", "批次刪除本分類下頁面"))).addClass("sysop-show"), $portletAnchor = $portlet.find("a");
    const pages = [];

    // Auto load flag status (for delcats)
    const isDelCat = mw.config.get("wgTitle") === DELCATS[location.hostname];
    if (isDelCat) {
        globalDeletionLock = true;
        $portletAnchor.text(wgULS("正在加载中……", "正在加載中……"));

        // Find all users with rollback perms (patroller+)
        const trustedUsers = await (async () => {
            const result = [];
            const eol = Symbol();
            let aufrom = undefined;
            while (aufrom !== eol) {
                const _result = await api.post({
                    action: "query",
                    assertuser: USERNAME,
                    list: "allusers",
                    aurights: "rollback",
                    aulimit: "max",
                    aufrom,
                });
                if (_result.continue) {
                    aufrom = _result.continue.aufrom;
                } else {
                    aufrom = eol;
                }
                result.push(..._result.query.allusers.map(({ name }) => name));
            }
            return result;
        })();

        // Query candidate pages in the delcat
        const candidatePages = await (async () => {
            const result = [];
            const eol = Symbol();
            let gcmcontinue = undefined;
            while (gcmcontinue !== eol) {
                const _result = await api.post({
                    action: "query",
                    assertuser: USERNAME,
                    format: "json",
                    rvprop: "user",
                    prop: "revisions",
                    generator: "categorymembers",
                    gcmtitle: PAGENAME,
                    gcmprop: "ids|title",
                    gcmtype: "page|subcat|file",
                    gcmlimit: "max",
                    gcmcontinue,
                });
                if (_result.continue) {
                    gcmcontinue = _result.continue.gcmcontinue;
                } else {
                    gcmcontinue = eol;
                }
                result.push(...Object.values(_result.query.pages));
            }
            return result.filter(({ title }) => document.querySelector(generatePageLinkSelector(title)));
        })();

        for (const { title, pageid, revisions: [{ user }] } of candidatePages) {
            for (let retryTimes = 0; retryTimes < 3; retryTimes++) {
                try {
                    const html = (await api.post({
                        action: "parse",
                        assertuser: USERNAME,
                        pageid,
                        prop: "text",
                    })).parse.text["*"];
                    const $html = $(html).children(".infoBox.will2Be2Deleted");
                    const $reason = $html.find("#reason"), $actor = $html.find("#actor a").first();
                    const reason = $reason.text().trim(), actor = $actor.text().trim();
                    const link = $(generatePageLinkSelector(title));
                    if ($reason.length === 1 && $actor.length === 1 && reason && actor) {
                        const isTrusted = user === actor && trustedUsers.includes(user);
                        pages.push({
                            title,
                            user,
                            isTrusted,
                            reason,
                        });
                        link.addClass("batdel-checked");
                        if (isTrusted) {
                            // Flag is trusted
                            link.after(`<div>${wgULS("挂删人", "掛刪人")}:<a href="/User:${user}" class="mw-userlink batdel-bypass"><bdi>${user}</bdi></a></div><div>${wgULS("挂删理由", "掛刪理由")}${reason}</div>`);
                        } else {
                            // Flag is not trusted, do not delete
                            link.prop("target", "_blank").after(`<div class="batdel-error">${wgULS("禁止删除:该次挂删不可靠,请手动检查", "禁止刪除:該次掛刪不可靠,請手動檢查")}${user !== $actor.text() ? wgULS("最后编辑者与挂删人不符", "最後編輯者與掛刪人不符") : wgULS("最后编辑者没有巡查权限", "最後編輯者沒有巡查權限")})</div>`);
                            console.warn(`[BatchDelete] ${title} does not have a trusted flag`);
                        }
                    } else {
                        pages.push({
                            title,
                            user: actor,
                            isTrusted: false,
                            reason,
                        });
                        link.addClass("batdel-bypass").prop("target", "_blank").after(`<div class="batdel-error">${wgULS("禁止删除:该次挂删不可靠,请手动检查(挂删模板未给出理由或挂删人)", "禁止刪除:該次掛刪不可靠,請手動檢查(掛刪模板未給出理由或掛刪人)")}</div>`);
                        console.warn(`[BatchDelete] ${title} has empty reason or actor`);
                    }
                    break;
                } catch (e) {
                    console.error("[BatchDelete]", e);
                }
            }
        }

        // For unprocessed links
        $items.find("a:not(.batdel-bypass, .batdel-checked)").each((_, ele) => {
            const $link = $(ele);
            $link.prop("target", "_blank").after(`<div class="batdel-error">${wgULS("禁止删除:无法获取页面挂删信息", "禁止刪除:無法獲取頁面掛刪信息")}</div>`);
            console.warn(`[BatchDelete] ${$link.text()} is not processed`);
        });

        globalDeletionLock = false;

        // Fire hook for userlink gadget
        mw.hook("wikipage.content").fire($(".mw-userlink.batdel-bypass"));
        // Restore portlet link text
        $portletAnchor.text(wgULS("批量删除本分类下页面", "批次刪除本分類下頁面"));
    }

    // Deletion buttons
    $portlet.on("click", () => {
        if ($("#batdel-control")[0] || globalDeletionLock) {
            return;
        }

        // Initialise UI
        const selectedNum = $("<span>0</span>"), totalNum = $("<span>?</span>");
        const toggleSelection = $(`<button>${wgULS("全选/全不选", "全選/全不選")}</button>`),
            runDeletion = $("<button>提交</button>"),
            cancelDeletion = $("<button>取消</button>");
        $control.empty().append([
            `${wgULS("请选择要删除的页面", "請選擇要刪除的頁面")} [`, selectedNum, "/", totalNum, "] ",
            toggleSelection, runDeletion, cancelDeletion,
        ]).prependTo($root);
        $("body").addClass("batdel-body");

        // Add checkboxes
        $items.each((_, ele) =>
            $(ele).prepend($("<input type='checkbox' class='batdel-select'>").prop("disabled", $(ele).find(".batdel-error")[0])),
        ).find(".stub").toggleClass("stub _stub");
        const checkboxes = $items.find(".batdel-select:not(:disabled)");
        totalNum.text(checkboxes.length);
        checkboxes.on("change", () =>
            selectedNum.text(checkboxes.filter(":checked").length),
        );
        $root.children("div").children("p").each((_, ele) => {
            $(`<button class="batdel-controlButton">${wgULS("全选/全不选本类别页面", "全選/全不選本類別頁面")}</button>`).on("click", (e) =>
                $(e.target).closest(".mw-category-generated > div").find(".batdel-select:not(:disabled)").each((_, ele) => $(ele).prop("checked", !ele.checked)).trigger("change"),
            ).appendTo(ele);
        });

        // Functional code for buttons
        toggleSelection.on("click", () => {
            $items.find(".batdel-select:not(:disabled)").each((_, ele) => $(ele).prop("checked", !ele.checked)).trigger("change");
        });
        cancelDeletion.on("click", () => {
            if (globalDeletionLock) {
                return;
            }
            $control.remove();
            $(".batdel-controlButton").remove();
            $root.find("._stub").toggleClass("stub _stub");
            $items.find(".batdel-select").remove();
            $(".batdel-disabled").removeClass("batdel-disabled");
            $("body").removeClass("batdel-body");
        });
        runDeletion.on("click", async () => {
            if (globalDeletionLock || !await oouiDialog.confirm(`${wgULS("您确定要删除这些页面吗?", "您確定要刪除這些頁面嗎?")}${wgULS("选中了", "選中了")}${$items.find(".batdel-select:checked").length}${wgULS("个页面", "個頁面")})`, {
                title: wgULS("批量删除分类页面工具", "批次刪除分類頁面工具"),
            })) {
                return;
            }

            let deletionReason = isDelCat
                ? ""
                : await oouiDialog.prompt(`${wgULS("请输入删除理由", "請輸入刪除理由")}`, {
                    title: wgULS("批量删除分类页面工具", "批次刪除分類頁面工具"),
                    size: "medium",
                    required: true,
                });

            // Temporary fix, remove after libOOUIDialog is fixed
            while (!isDelCat && deletionReason === "") {
                deletionReason = await oouiDialog.prompt(`${wgULS("请输入删除理由", "請輸入刪除理由")}`, {
                    title: wgULS("批量删除分类页面工具", "批次刪除分類頁面工具"),
                    size: "medium",
                    required: true,
                });
            }

            if (deletionReason === null) {
                return;
            }
            deletionReason = deletionReason ? `(${deletionReason})` : "";

            // eslint-disable-next-line require-atomic-updates
            globalDeletionLock = true;

            const $spinner = $('<img src="https://img.moegirl.org.cn/common/d/d1/Windows_10_loading.gif" style="height: 1em; margin-top: -.25em;">'), $status = $("<span>");

            $root.find(".batdel-result").remove();
            $root.find(".batdel-select").prop("disabled", true);
            $control.append("<br>", $spinner, $status);
            $root.find("a:not(.batdel-bypass)").each((_, ele) => {
                const self = $(ele);
                if (!self.closest("li").find(".batdel-select:checked")[0]) {
                    self.addClass("batdel-disabled");
                }
            });
            try {
                $status.text(wgULS("正在删除,已完成删除的页面将会被删除线划去……", "正在刪除,已完成刪除的頁面將會被刪除線划去……"));
                for (const ele of $root.find("a").not(".batdel-bypass, .batdel-disabled").toArray()) {
                    const self = $(ele);
                    if (!self.text().trim()) {
                        return;
                    }
                    self.css("margin-right", "1em");
                    const url = new URL(new mw.Uri(self.prop("href")));
                    const target = decodeURIComponent(url.searchParams.has("title") ? url.searchParams.get("title") : url.pathname.replace(/^\//, "")).replace(/_/g, " ");
                    const page = pages.filter(({ title }) => title === target)[0];
                    try {
                        await api.postWithToken("csrf", {
                            action: "delete",
                            assertuser: USERNAME,
                            format: "json",
                            title: target,
                            tags: "Automation tool",
                            reason: `批量删除【${PAGENAME}】下的页面${isDelCat && page.isTrusted && page.reason && page.user ? `([[User_talk:${page.user}|${page.user}]]的挂删理由:${page.reason} )` : deletionReason}`,
                        }, {
                            timeout: 99999,
                        });
                        self.css("text-decoration", "line-through").after(`<span class="batdel-result batdel-success">${wgULS("删除成功", "刪除成功")}</span>`);
                    } catch (e) {
                        self.after(`<span class="batdel-result batdel-error"> ${wgULS("删除失败", "刪除失敗")}${e instanceof Error ? `${e} ${e.stack.split("\n")[1].trim()}` : JSON.stringify(e)}</span>`);
                    }
                }
                $spinner.remove();
                $status.addClass("batdel-success").text(wgULS("删除已完成!", "刪除已完成!"));
            } catch (e) {
                $spinner.remove();
                $status.text(`${wgULS("发生错误", "發生錯誤")}${e instanceof Error ? `${e} ${e.stack.split("\n")[1].trim()}` : JSON.stringify(e)}`);
            }
        });

        // Prevent default
        return false;
    });
})());
// </pre>