User:MonkeysHK/common.js

/** Staff Tagger * * Code to automate some part of administrator's actions for staff promotion/demotion * * Staff "tag" option appears on the template * This template is commonly used on HSW:STFREQS */

/* jshint esversion: 5, forin: true, immed: true, indent: 4, latedef: true, newcap: true, noarg: true, undef: true, undef: true, unused: true, browser: true, jquery: true, onevar: true, eqeqeq: true, multistr: true, maxerr: 999999, sub: true, forin: false, -W082, -W084

/* global mw, BannerNotification */ "use strict"; (function {    if (window.StaffTagger && window.StaffTagger.Loaded)        return;

window.StaffTagger = window.StaffTagger || {}; window.StaffTagger.Loaded = true;

console.log("[User:MonkeysHK/common.js] Test script loading."); $.when(       mw.loader.using([ "mediawiki.util", "mediawiki.api", mw.loader.getModuleNames.find(function (module) {               return module.startsWith("BannerNotification");            }), ]),       $.Deferred(function (def) { mw.hook("dev.modal").add(function {                def.resolve;            }); }),       $.Deferred(function (def) { if (mw.util.StaffUtil) { def.resolve(mw.util.StaffUtil); } else { $.ajax({                   cache: true,                    dataType: "script",                    url: "https://hypixel-skyblock.fandom.com/wiki/MediaWiki:Gadget-StaffColorsUpdater.js"                }).done(function  {                    def.resolve(mw.util.StaffUtil);                }); }       })    ).then(function  {        if (!/codeeditor|sysop|bureaucrat|soap|staff|helper|wiki-manager|content-team-member|util/.test(mw.config.get("wgUserGroups").join("\n")))            return;

window.StaffTagger = window.StaffTagger || {};

var StaffTagger, that, cachedStaffData; StaffTagger = that = window.StaffTagger = Object.assign(this, {           Loaded: true,            api: new mw.Api,            staffDataPage: "Module:Staff/Members",            ranksAvailable: [                "bureaucrat",                "sysop",                "codeeditor",                "content-moderator",                "threadmoderator",                "rollback",            ],            rankShorts: {                bureaucrat: "BU",                sysop: "AD",                codeeditor: "CE",                "content-moderator": "CM",                threadmoderator: "DM",                rollback: "RB",            },            rankLongs: {                BU: "bureaucrat",                AD: "sysop",                CE: "codeeditor",                CM: "content-moderator",                DM: "threadmoderator",                RB: "rollback",            },

// Begin (Taken from HypixelItemDataFetcher (by Fewfre) and modified) luaTableDataModuleToJson: function (moduleName) { return that.api.post({                       action: "scribunto-console",                        title: mw.config.get("wgPageName"),                        question: "=mw.text.jsonEncode(require('" + moduleName + "'))"                    }) .then(function (response) {                       return response.return;                    }) .then(function (data) {                       return JSON.parse(data);                    }); },           toSafeStr: function (s) { return s.replaceAll("\'", "\\\'").replaceAll("\"", "\\\""); },           toLuaKey: function (k) { k = that.toSafeStr(k); return (/^\w+$/.test(k) && /^[^\d]/.test(k)) ? k : ("[\"" + k + "\"]"); },           jsonToLuaTable: function (json, depth) { depth = typeof depth === "undefined" ? -1 : depth; depth++; if (Array.isArray(json)) { return "{ " + json.map(function (o) {                       return that.jsonToLuaTable(o, depth);                    }).join(", ") + " }"; } else if (typeof json === "object") { var space = depth ? "" : "\t", br = depth ? "" : "\n", ex = depth ? " " : "";                   return "{ " + br + Object.entries(json).sort(function (a, b) {                           return a[0] > b[0];                        }).map(function (data) {                            return space + that.toLuaKey(data[0]) + " = " + that.jsonToLuaTable(data[1], depth);                        }).join(", " + br) + br + ex + "}"; }               // Otherwise seems to be normal value; done on this branch! else { return typeof json === "string" ? "\"" + that.toSafeStr(json) + "\"" : json; }           },            saveToWiki: function (json, summary, page) { var lua = "-- \nreturn " + that.jsonToLuaTable(json);

return that.api.postWithEditToken({                   action: "edit",                    text: lua,                    title: page,                    summary: summary,                    minor: true,                }).catch(console.warn); },           // End (Taken from HypixelItemDataFetcher (by Fewfre) and modified)

fetchStaffData: function { return that.luaTableDataModuleToJson(that.staffDataPage).then(function (data) {                   cachedStaffData = data;                }); },           fetchUserRights: function (user) { return that.api.get({                   action: "query",                    list: "users",                    ususers: user,                    usprop: "groupmemberships",                    aulimit: 500,                }).then(function (dt) {                    var result = dt.query.users[0].groupmemberships.map(function (v) { return v.expiry === "infinity" ? v.group : ""; });                   that.existingGroups = [];                    for (var i in that.ranksAvailable)                        if (result.includes(that.ranksAvailable[i]))                            that.existingGroups.push(that.ranksAvailable[i]);                }).catch(console.warn); },

putToday: function (e) { e.preventDefault; var date = new Date; $(this).siblings("input").val(date.getUTCFullYear + "-" + (date.getUTCMonth + 1) + "-" + date.getUTCDate); },           getDefaultDate: function (user) { return (!cachedStaffData[user] || !cachedStaffData[user]["join"]) ? "" : cachedStaffData[user]["join"]; },           getDefaultResignDate: function (user) { return (!cachedStaffData[user] || !cachedStaffData[user]["resignation"]) ? "" : cachedStaffData[user]["resignation"]; },

// #1: Granting rights (if applicable) grantUserRights: function (user, ranks_to_add, ranks_to_remove, reason) { return that.api.get({                   action: "query",                    list: "users",                    ususers: user,                    ustoken: "userrights",                }).always(function (data) {                    var usToken = data.query.users[0].userrightstoken;                    if (data.query) {                        new mw.Api.post({ "action": "userrights", "format": "json", "user": user, "add": ranks_to_add, "remove": ranks_to_remove, "expiry": "infinite", "reason": reason, "token": usToken, }).always(function (data) { if (data.userrights) { console.log("[StaffTagger #1]: Finished modified ranks (when applicable)!"); console.log("Ranks added (" + data.userrights.added.length + "): ", data.userrights.added); console.log("Ranks removed (" + data.userrights.removed.length + "): ", data.userrights.removed); } else console.warn("[StaffTagger #1]: Failed to modify ranks:" + data); });                   } else                        console.warn("[StaffTagger #1]: API error in getting user rights token:" + data, "(API Error code \"" + data + "\")");                }); },

// #2: Change Staff Data on Staff Page // Now should update Module:Staff/Members instead changeStaffData: function (user, ranklist, joindate, resigndate, reason) { var shorts = []; for (var i in that.ranksAvailable) if (ranklist.includes(that.ranksAvailable[i])) shorts.push(that.rankShorts[that.ranksAvailable[i]]);

shorts = shorts.length && shorts.join("|") || undefined; cachedStaffData[user] = cachedStaffData[user] || {}; if (resigndate) Object.assign(cachedStaffData[user], {                       rank: undefined,                        former: shorts,                        join: joindate,                        resignation: resigndate,                    }); else Object.assign(cachedStaffData[user], {                       rank: shorts,                        join: joindate,                    });

return that.saveToWiki(cachedStaffData, reason, that.staffDataPage).always(function (data) {                   if (data.edit)                        console.log("[StaffTagger #2]: Successfully added" + user + " to staff member list");                    else                        console.warn("[StaffTagger #2]: API Error in editing staff member list", user + ":", data);                }); },

// #3: Add staff message box to userpage addMsgBox: function (user, reason, isResign) { var userpage = "User:" + user, newtext = "\n"; return that.api.get({                   action: "query",                    format: "json",                    prop: "revisions",                    titles: userpage,                    formatversion: 2,                    rvprop: "content",                    rvslots: "*",                }).then(function (d) {                    var content = d.query.pages[0].revisions[0].slots.main.content;                    var regex = new RegExp("\\{\\{StaffM?s?g?b?o?x?\\|.*?\\}\\}\\s*", "ig");                    var options = {                        action: "edit",                        watchlist: "nochange",                        summary: reason,                        title: userpage,                        minor: true,                        prependtext: newtext,                        token: mw.user.tokens.values.editToken,                    };                    if (content.match(regex)) { delete options.prependtext; options.text = newtext + content.replace(regex, ""); console.log("[StaffTagger #3]: Replaced existing StaffMsgbox"); }                   return that.api.post(options); }).always(function (data) { if (data.edit) console.log("[StaffTagger #3]: Successfully added StaffMsgbox to", user); else console.warn("[StaffTagger #3]: API Error in adding StaffMsgbox for", user + ":", data); });           },

// #4: Protect staff userpage protectStaffPage: function (user, highLevel) { var userpage = "User:" + user; return that.api.post({                   action: "protect",                    bot: true,                    watchlist: "nochange",                    title: userpage,                    protections: (highLevel) ?                        "edit=sysop|move=sysop" : "edit=autoconfirmed|move=autoconfirmed",                    expiry: "infinite",                    reason: "Protecting wiki staff userpage",                    token: mw.user.tokens.values.editToken,                }).always(function (data) {                    if (data.protect)                        console.log("[StaffTagger #4]: Successfully protected", user + "\'s userpage");                    else                        console.warn("[StaffTagger #4]: API error in protecting", user + "\'s userpage:", data);                }); },

// #5: Update staff-colors.less updateStaffColors: function { return mw.util.StaffUtil.updateStaffColors(function {                    console.log("[StaffTagger #5]: Successfully updated staff-colors.less");                }); },

isValidDate: function (s) { return /\d{4}\-\d{1,2}\-\d{1,2}/.test(s) && (!isNaN((new Date(s)).getDate)) },

processInformation: function { var mode = that.usingMode, errorEl = $("#StaffTagger-error"), opts;

errorEl.empty; if (mode === "staff") { opts = { user: $("#StaffTagger-user-input").val.trim, ranklist: $("#StaffTagger-ranks-wrapper *:checked").get.map(function (el) {                           return $(el).attr("data-rank");                        }), date: $("#StaffTagger-date-input").val.trim, joindate: stf.date, reason: $("#StaffTagger-reason-input").val.trim, highLevel: /content-moderator|codeeditor|sysop|bureaucrat/.test(ranks), };                   opts.addedRanks = ranklist.join("|"); opts.removedRanks = []; for (var i in existingGroups) { if (!opt.ranklist.includes(existingGroups[i])) opts.removedRanks.push(existingGroups[i]); }                   if (!opts.ranklist.length && !window.confirm("No ranks are selected for this user. Are you sure to proceed?\nNote: For staff resignation, click \"CANCEL\" and \"Switch to Resign Mode\".")) return; } else if (mode === "resign") { opts = { user: $("#StaffTagger-user-input").val.trim, ranklist: $("#StaffTagger-ranks-wrapper-resign *:checked").get.map(function (el) {                           return $(el).attr("data-rank");                        }), date: $("#StaffTagger-date-input").val.trim, resignDate: $("#StaffTagger-date-input-resign").val.trim, reason: $("#StaffTagger-reason-input-resign").val.trim, highLevel: false, }                   removedRanks = that.ranksAvailable.join("|"); if (!opts.ranklist.length && !window.confirm("No former ranks are selected. Are you sure to proceed?")) return; }               if (!isValidDate(opts.date)) return errorEl.text("Join date is invalid."); if (opts.resignDate && !isValidDate(opts.resignDate)) return errorEl.text("Resign date is invalid."); if (opts.user === "") return errorEl.text("Please input a user.");

Promise.all([                   that.grantUserRights(opts.user, opts.addedRanks, opts.removedRanks, opts.reason), // #1                    // that.changeStaffData(opts.user, opts.ranklist, opts.date, opts.resignDate, opts.reason), // #2                    // that.addMsgBox(opts.user, opts.reason, !!opts.resignDate), // #3                    // that.protectStaffPage(opts.user, opts.highLevel), // #4                    // that.updateStaffColors, // #5                ]).then(that.onResolve);

that.modal.hide; new BannerNotification($(" ", { html: " Processing Another pop-up should indicate completion. ", }).prop("outerHTML"), "confirm").show; },

onResolve: function { new BannerNotification($(" ", { html: " All subprocesses completed See Console for detailed results You may now Update CSS. ", }).prop("outerHTML"), "confirm").show; },

editorCloseHandler: function { that.modal.hide; },

createIntroSection: function { return "Select the ranks for this user. Then, the Staff Tagger will perform the following:\	\	Grant selected rights to the user on wiki (if applicable)\	Change staff data on Module:Staff/Members\	Add StaffMsgBox to the user page\	Protect the user page\	Update staff-color.less</li>\	Null Edit  and   for the user</li>\	</ol>\	"; },

createRanksSelection: function (suffix) { return that.ranksAvailable.map(function (rank) {                   var idname = "StaffTagger-rank-" + rank + suffix;                    return {                        type: "div",                        attr: {                            id: "StaffTagger-ranks-wrapper" + suffix                        },                        children: [{                            type: "input",                            attr: {                                type: "checkbox",                                id: idname,                                "data-rank": rank                            },                        }, {                            type: "label",                            attr: {                                for: idname                            },                            text: " " + rank,                        }],                    };                }); },

showModal: function { var user = that.user || ""; Promise.all([                   that.fetchStaffData,                    that.fetchUserRights(user),                ]).then(function  {                    var defaultDate = that.getDefaultDate(user),                        defaultResignDate = that.getDefaultResignDate(user),                        defaultReason = "Per request to be staff: HSW:STFREQS",                        defaultResignReason = "Staff resignation of User:" + user + "";                    that.modal.setContent({ children: [{ type: "form", attr: { id: "StaffTagger-form", style: "margin: 0 1em;", },                           children: [{ type: "span", attr: { class: "page-content", },                               html: that.createIntroSection, }, {                               type: "div", id: "StaffTagger-input", children: [{ type: "div", attr: { id: "StaffTagger-error", style: "color: red;", },                               }, {                                    type: "div", attr: { id: "StaffTagger-user-wrapper", // This needs to appear in both staff/resign modes },                                   children: [{ type: "label", id: "StaffTagger-user-label", text: "User: ", }, {                                       type: "input", attr: { id: "StaffTagger-user-input", type: "text", value: user, },                                   }],                                }, {                                    type: "div", attr: { id: "StaffTagger-ranks-wrapper", class: "staff-components", },                                   children: [{ type: "div", id: "StaffTagger-ranks-label", text: "Select the ranks to add (Deselect the ranks to remove):", }].concat(that.createRanksSelection("")), }, {                                   type: "div", attr: { id: "StaffTagger-ranks-wrapper-resign", class: "resign-components", },                                   children: [{ type: "div", id: "StaffTagger-ranks-label-resign", html: "Important: All ranks will be removed from this user (if applicable). Select previous highest rank(s) of this user:", }].concat(that.createRanksSelection("-resign")), }, {                                   type: "div", attr: { id: "StaffTagger-date-wrapper", // This needs to appear in both staff/resign modes },                                   children: [{ type: "label", id: "StaffTagger-date-label", text: "Join Date: ", }, {                                       type: "input", attr: { id: "StaffTagger-date-input", type: "text", value: defaultDate, },                                   }, {                                        type: "a", attr: { id: "StaffTagger-date-today", type: "text", href: "#", },                                       html: "[Today]", }],                               }, {                                    type: "div", attr: { id: "StaffTagger-date-wrapper-resign", class: "resign-components", },                                   children: [{ type: "label", id: "StaffTagger-date-label-resign", text: "Resign Date: ", }, {                                       type: "input", attr: { id: "StaffTagger-date-input-resign", type: "text", value: defaultResignDate, },                                   }, {                                        type: "a", attr: { id: "StaffTagger-date-today-resign", type: "text", href: "#", },                                       html: "[Today]", }],                               }, {                                    type: "div", attr: { id: "StaffTagger-reason-wrapper", class: "staff-components", },                                   children: [{ type: "label", id: "StaffTagger-reason-label", text: "Reason: ", }, {                                       type: "input", attr: { id: "StaffTagger-reason-input", type: "text", value: defaultReason, },                                   }],                                }, {                                    type: "div", attr: { id: "StaffTagger-reason-wrapper-resign", class: "resign-components", },                                   children: [{ type: "label", id: "StaffTagger-reason-label-resign", text: "Reason: ", }, {                                       type: "input", attr: { id: "StaffTagger-reason-input-resign", type: "text", value: defaultResignReason, },                                   }],                                }],                            }],                        }]                    });                    that.modal.show;                    that.toStaffMode;                    for (var i in that.existingGroups)                        $("#StaffTagger-rank-" + that.existingGroups[i]).prop("checked", true);                    var former = cachedStaffData[user] && cachedStaffData[user]["former"] && cachedStaffData[user]["former"].split("|") || []                    if (!(former.length === 1 && former[0] === ""))                        for (var i in former)                            if (rankLongs[former[i]])                                $("#StaffTagger-rank-" + rankLongs[former[i]] + "-resign").prop("checked", true);                    $("#StaffTagger-date-today").click(that.putToday); $("#StaffTagger-date-today-resign").click(that.putToday); });           },            onclick: function (e) {                e.preventDefault;                that.user = (mw.config.get("wgNamespaceNumber") !== 2) ? $(this).parent.attr("data-user") :                    mw.config.get("wgPageName").match(mw.config.get("wgFormattedNamespaces")[2] + ":([^/]*)")[1];                that.showModal;            },            init: function  {                $("span#tag-user > a[href*=\"prepend=\"].text").click(that.onclick);                if (mw.config.get("wgNamespaceNumber") === 2) {                    $(".page-header__contribution-buttons .wds-list, .page-header__actions .wds-list").first.append( $("").append(                           $("", { text: "Staff Tagger", href: "#", click: that.onclick, })                       )                    );                }            },

toStaffMode: function { that.usingMode = "staff"; $(".resign-components").hide; $(".staff-components").show; },

toResignMode: function { that.usingMode = "resign"; $(".staff-components").hide; $(".resign-components").show; },       });

Object.assign(this, {           modal: new window.dev.modal.Modal({ id: "StaffTagger-modal", title: "Staff Tagger", content: "", buttons: [{ event: "onSubmit", id: "StaffTagger-submit", primary: true, text: "Submit", }, {                   event: "onStaffMode", id: "StaffTagger-staffmode", text: "Switch to Staff Mode", }, {                   event: "onResignMode", id: "StaffTagger-resignmode", text: "Switch to Resign Mode", }],               events: { onSubmit: this.processInformation, onStaffMode: this.toStaffMode, onResignMode: this.toResignMode, },               size: "large", }),       });        that.modal.create;

that.init; }); });