Jump to content

User:Firefly/more-block-info.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Firefly (talk | contribs) at 15:57, 21 December 2021 (upd). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// ipaddr.js, MIT licensed
// see https://github.com/whitequark/ipaddr.js/blob/master/LICENSE for license and contributors
!(function (t) {
    !(function (t) {
        "use strict";
        const r = "(0?\\d+|0x[a-f0-9]+)",
            e = {
                fourOctet: new RegExp(`^${r}\\.${r}\\.${r}\\.${r}$`, "i"),
                threeOctet: new RegExp(`^${r}\\.${r}\\.${r}$`, "i"),
                twoOctet: new RegExp(`^${r}\\.${r}$`, "i"),
                longValue: new RegExp(`^${r}$`, "i"),
            },
            n = new RegExp("^0[0-7]+$", "i"),
            i = new RegExp("^0x[a-f0-9]+$", "i"),
            o = "(?:[0-9a-f]+::?)+",
            s = {
                zoneIndex: new RegExp("%[0-9a-z]{1,}", "i"),
                native: new RegExp(`^(::)?(${o})?([0-9a-f]+)?(::)?(%[0-9a-z]{1,})?$`, "i"),
                deprecatedTransitional: new RegExp(`^(?:::)(${r}\\.${r}\\.${r}\\.${r}(%[0-9a-z]{1,})?)$`, "i"),
                transitional: new RegExp(`^((?:${o})|(?:::)(?:${o})?)${r}\\.${r}\\.${r}\\.${r}(%[0-9a-z]{1,})?$`, "i"),
            };
        function a(t, r) {
            if (t.indexOf("::") !== t.lastIndexOf("::")) return null;
            let e,
                n,
                i = 0,
                o = -1,
                a = (t.match(s.zoneIndex) || [])[0];
            for (a && ((a = a.substring(1)), (t = t.replace(/%.+$/, ""))); (o = t.indexOf(":", o + 1)) >= 0; ) i++;
            if (("::" === t.substr(0, 2) && i--, "::" === t.substr(-2, 2) && i--, i > r)) return null;
            for (n = r - i, e = ":"; n--; ) e += "0:";
            return (
                ":" === (t = t.replace("::", e))[0] && (t = t.slice(1)),
                ":" === t[t.length - 1] && (t = t.slice(0, -1)),
                {
                    parts: (r = (function () {
                        const r = t.split(":"),
                            e = [];
                        for (let t = 0; t < r.length; t++) e.push(parseInt(r[t], 16));
                        return e;
                    })()),
                    zoneId: a,
                }
            );
        }
        function p(t, r, e, n) {
            if (t.length !== r.length) throw new Error("ipaddr: cannot match CIDR for objects with different lengths");
            let i,
                o = 0;
            for (; n > 0; ) {
                if (((i = e - n) < 0 && (i = 0), t[o] >> i != r[o] >> i)) return !1;
                (n -= e), (o += 1);
            }
            return !0;
        }
        function u(t) {
            if (i.test(t)) return parseInt(t, 16);
            if ("0" === t[0] && !isNaN(parseInt(t[1], 10))) {
                if (n.test(t)) return parseInt(t, 8);
                throw new Error(`ipaddr: cannot parse ${t} as octal`);
            }
            return parseInt(t, 10);
        }
        function d(t, r) {
            for (; t.length < r; ) t = `0${t}`;
            return t;
        }
        const c = {};
        (c.IPv4 = (function () {
            function t(t) {
                if (4 !== t.length) throw new Error("ipaddr: ipv4 octet count should be 4");
                let r, e;
                for (r = 0; r < t.length; r++) if (!(0 <= (e = t[r]) && e <= 255)) throw new Error("ipaddr: ipv4 octet should fit in 8 bits");
                this.octets = t;
            }
            return (
                (t.prototype.SpecialRanges = {
                    unspecified: [[new t([0, 0, 0, 0]), 8]],
                    broadcast: [[new t([255, 255, 255, 255]), 32]],
                    multicast: [[new t([224, 0, 0, 0]), 4]],
                    linkLocal: [[new t([169, 254, 0, 0]), 16]],
                    loopback: [[new t([127, 0, 0, 0]), 8]],
                    carrierGradeNat: [[new t([100, 64, 0, 0]), 10]],
                    private: [
                        [new t([10, 0, 0, 0]), 8],
                        [new t([172, 16, 0, 0]), 12],
                        [new t([192, 168, 0, 0]), 16],
                    ],
                    reserved: [
                        [new t([192, 0, 0, 0]), 24],
                        [new t([192, 0, 2, 0]), 24],
                        [new t([192, 88, 99, 0]), 24],
                        [new t([198, 51, 100, 0]), 24],
                        [new t([203, 0, 113, 0]), 24],
                        [new t([240, 0, 0, 0]), 4],
                    ],
                }),
                (t.prototype.kind = function () {
                    return "ipv4";
                }),
                (t.prototype.match = function (t, r) {
                    let e;
                    if ((void 0 === r && ((t = (e = t)[0]), (r = e[1])), "ipv4" !== t.kind()))
                        throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one");
                    return p(this.octets, t.octets, 8, r);
                }),
                (t.prototype.prefixLengthFromSubnetMask = function () {
                    let t = 0,
                        r = !1;
                    const e = {
                        0: 8,
                        128: 7,
                        192: 6,
                        224: 5,
                        240: 4,
                        248: 3,
                        252: 2,
                        254: 1,
                        255: 0,
                    };
                    let n, i, o;
                    for (n = 3; n >= 0; n -= 1) {
                        if (!((i = this.octets[n]) in e)) return null;
                        if (((o = e[i]), r && 0 !== o)) return null;
                        8 !== o && (r = !0), (t += o);
                    }
                    return 32 - t;
                }),
                (t.prototype.range = function () {
                    return c.subnetMatch(this, this.SpecialRanges);
                }),
                (t.prototype.toByteArray = function () {
                    return this.octets.slice(0);
                }),
                (t.prototype.toIPv4MappedAddress = function () {
                    return c.IPv6.parse(`::ffff:${this.toString()}`);
                }),
                (t.prototype.toNormalizedString = function () {
                    return this.toString();
                }),
                (t.prototype.toString = function () {
                    return this.octets.join(".");
                }),
                t
            );
        })()),
            (c.IPv4.broadcastAddressFromCIDR = function (t) {
                try {
                    const r = this.parseCIDR(t),
                        e = r[0].toByteArray(),
                        n = this.subnetMaskFromPrefixLength(r[1]).toByteArray(),
                        i = [];
                    let o = 0;
                    for (; o < 4; ) i.push(parseInt(e[o], 10) | (255 ^ parseInt(n[o], 10))), o++;
                    return new this(i);
                } catch (t) {
                    throw new Error("ipaddr: the address does not have IPv4 CIDR format");
                }
            }),
            (c.IPv4.isIPv4 = function (t) {
                return null !== this.parser(t);
            }),
            (c.IPv4.isValid = function (t) {
                try {
                    return new this(this.parser(t)), !0;
                } catch (t) {
                    return !1;
                }
            }),
            (c.IPv4.isValidFourPartDecimal = function (t) {
                return !(!c.IPv4.isValid(t) || !t.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/));
            }),
            (c.IPv4.networkAddressFromCIDR = function (t) {
                let r, e, n, i, o;
                try {
                    for (n = (r = this.parseCIDR(t))[0].toByteArray(), o = this.subnetMaskFromPrefixLength(r[1]).toByteArray(), i = [], e = 0; e < 4; )
                        i.push(parseInt(n[e], 10) & parseInt(o[e], 10)), e++;
                    return new this(i);
                } catch (t) {
                    throw new Error("ipaddr: the address does not have IPv4 CIDR format");
                }
            }),
            (c.IPv4.parse = function (t) {
                const r = this.parser(t);
                if (null === r) throw new Error("ipaddr: string is not formatted like an IPv4 Address");
                return new this(r);
            }),
            (c.IPv4.parseCIDR = function (t) {
                let r;
                if ((r = t.match(/^(.+)\/(\d+)$/))) {
                    const t = parseInt(r[2]);
                    if (t >= 0 && t <= 32) {
                        const e = [this.parse(r[1]), t];
                        return (
                            Object.defineProperty(e, "toString", {
                                value: function () {
                                    return this.join("/");
                                },
                            }),
                            e
                        );
                    }
                }
                throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range");
            }),
            (c.IPv4.parser = function (t) {
                let r, n, i;
                if ((r = t.match(e.fourOctet)))
                    return (function () {
                        const t = r.slice(1, 6),
                            e = [];
                        for (let r = 0; r < t.length; r++) (n = t[r]), e.push(u(n));
                        return e;
                    })();
                if ((r = t.match(e.longValue))) {
                    if ((i = u(r[1])) > 4294967295 || i < 0) throw new Error("ipaddr: address outside defined range");
                    return (function () {
                        const t = [];
                        let r;
                        for (r = 0; r <= 24; r += 8) t.push((i >> r) & 255);
                        return t;
                    })().reverse();
                }
                return (r = t.match(e.twoOctet))
                    ? (function () {
                          const t = r.slice(1, 4),
                              e = [];
                          if ((i = u(t[1])) > 16777215 || i < 0) throw new Error("ipaddr: address outside defined range");
                          return e.push(u(t[0])), e.push((i >> 16) & 255), e.push((i >> 8) & 255), e.push(255 & i), e;
                      })()
                    : (r = t.match(e.threeOctet))
                    ? (function () {
                          const t = r.slice(1, 5),
                              e = [];
                          if ((i = u(t[2])) > 65535 || i < 0) throw new Error("ipaddr: address outside defined range");
                          return e.push(u(t[0])), e.push(u(t[1])), e.push((i >> 8) & 255), e.push(255 & i), e;
                      })()
                    : null;
            }),
            (c.IPv4.subnetMaskFromPrefixLength = function (t) {
                if ((t = parseInt(t)) < 0 || t > 32) throw new Error("ipaddr: invalid IPv4 prefix length");
                const r = [0, 0, 0, 0];
                let e = 0;
                const n = Math.floor(t / 8);
                for (; e < n; ) (r[e] = 255), e++;
                return n < 4 && (r[n] = (Math.pow(2, t % 8) - 1) << (8 - (t % 8))), new this(r);
            }),
            (c.IPv6 = (function () {
                function t(t, r) {
                    let e, n;
                    if (16 === t.length) for (this.parts = [], e = 0; e <= 14; e += 2) this.parts.push((t[e] << 8) | t[e + 1]);
                    else {
                        if (8 !== t.length) throw new Error("ipaddr: ipv6 part count should be 8 or 16");
                        this.parts = t;
                    }
                    for (e = 0; e < this.parts.length; e++)
                        if (!(0 <= (n = this.parts[e]) && n <= 65535)) throw new Error("ipaddr: ipv6 part should fit in 16 bits");
                    r && (this.zoneId = r);
                }
                return (
                    (t.prototype.SpecialRanges = {
                        unspecified: [new t([0, 0, 0, 0, 0, 0, 0, 0]), 128],
                        linkLocal: [new t([65152, 0, 0, 0, 0, 0, 0, 0]), 10],
                        multicast: [new t([65280, 0, 0, 0, 0, 0, 0, 0]), 8],
                        loopback: [new t([0, 0, 0, 0, 0, 0, 0, 1]), 128],
                        uniqueLocal: [new t([64512, 0, 0, 0, 0, 0, 0, 0]), 7],
                        ipv4Mapped: [new t([0, 0, 0, 0, 0, 65535, 0, 0]), 96],
                        rfc6145: [new t([0, 0, 0, 0, 65535, 0, 0, 0]), 96],
                        rfc6052: [new t([100, 65435, 0, 0, 0, 0, 0, 0]), 96],
                        "6to4": [new t([8194, 0, 0, 0, 0, 0, 0, 0]), 16],
                        teredo: [new t([8193, 0, 0, 0, 0, 0, 0, 0]), 32],
                        reserved: [[new t([8193, 3512, 0, 0, 0, 0, 0, 0]), 32]],
                    }),
                    (t.prototype.isIPv4MappedAddress = function () {
                        return "ipv4Mapped" === this.range();
                    }),
                    (t.prototype.kind = function () {
                        return "ipv6";
                    }),
                    (t.prototype.match = function (t, r) {
                        let e;
                        if ((void 0 === r && ((t = (e = t)[0]), (r = e[1])), "ipv6" !== t.kind()))
                            throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one");
                        return p(this.parts, t.parts, 16, r);
                    }),
                    (t.prototype.prefixLengthFromSubnetMask = function () {
                        let t = 0,
                            r = !1;
                        const e = {
                            0: 16,
                            32768: 15,
                            49152: 14,
                            57344: 13,
                            61440: 12,
                            63488: 11,
                            64512: 10,
                            65024: 9,
                            65280: 8,
                            65408: 7,
                            65472: 6,
                            65504: 5,
                            65520: 4,
                            65528: 3,
                            65532: 2,
                            65534: 1,
                            65535: 0,
                        };
                        let n, i;
                        for (let o = 7; o >= 0; o -= 1) {
                            if (!((n = this.parts[o]) in e)) return null;
                            if (((i = e[n]), r && 0 !== i)) return null;
                            16 !== i && (r = !0), (t += i);
                        }
                        return 128 - t;
                    }),
                    (t.prototype.range = function () {
                        return c.subnetMatch(this, this.SpecialRanges);
                    }),
                    (t.prototype.toByteArray = function () {
                        let t;
                        const r = [],
                            e = this.parts;
                        for (let n = 0; n < e.length; n++) (t = e[n]), r.push(t >> 8), r.push(255 & t);
                        return r;
                    }),
                    (t.prototype.toFixedLengthString = function () {
                        const t = function () {
                            const t = [];
                            for (let r = 0; r < this.parts.length; r++) t.push(d(this.parts[r].toString(16), 4));
                            return t;
                        }
                            .call(this)
                            .join(":");
                        let r = "";
                        return this.zoneId && (r = `%${this.zoneId}`), t + r;
                    }),
                    (t.prototype.toIPv4Address = function () {
                        if (!this.isIPv4MappedAddress()) throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4");
                        const t = this.parts.slice(-2),
                            r = t[0],
                            e = t[1];
                        return new c.IPv4([r >> 8, 255 & r, e >> 8, 255 & e]);
                    }),
                    (t.prototype.toNormalizedString = function () {
                        const t = function () {
                            const t = [];
                            for (let r = 0; r < this.parts.length; r++) t.push(this.parts[r].toString(16));
                            return t;
                        }
                            .call(this)
                            .join(":");
                        let r = "";
                        return this.zoneId && (r = `%${this.zoneId}`), t + r;
                    }),
                    (t.prototype.toRFC5952String = function () {
                        const t = /((^|:)(0(:|$)){2,})/g,
                            r = this.toNormalizedString();
                        let e,
                            n = 0,
                            i = -1;
                        for (; (e = t.exec(r)); ) e[0].length > i && ((n = e.index), (i = e[0].length));
                        return i < 0 ? r : `${r.substring(0, n)}::${r.substring(n + i)}`;
                    }),
                    (t.prototype.toString = function () {
                        return this.toNormalizedString().replace(/((^|:)(0(:|$))+)/, "::");
                    }),
                    t
                );
            })()),
            (c.IPv6.isIPv6 = function (t) {
                return null !== this.parser(t);
            }),
            (c.IPv6.isValid = function (t) {
                if ("string" == typeof t && -1 === t.indexOf(":")) return !1;
                try {
                    const r = this.parser(t);
                    return new this(r.parts, r.zoneId), !0;
                } catch (t) {
                    return !1;
                }
            }),
            (c.IPv6.parse = function (t) {
                const r = this.parser(t);
                if (null === r.parts) throw new Error("ipaddr: string is not formatted like an IPv6 Address");
                return new this(r.parts, r.zoneId);
            }),
            (c.IPv6.parseCIDR = function (t) {
                let r, e, n;
                if ((e = t.match(/^(.+)\/(\d+)$/)) && (r = parseInt(e[2])) >= 0 && r <= 128)
                    return (
                        (n = [this.parse(e[1]), r]),
                        Object.defineProperty(n, "toString", {
                            value: function () {
                                return this.join("/");
                            },
                        }),
                        n
                    );
                throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range");
            }),
            (c.IPv6.parser = function (t) {
                let r, e, n, i, o, p;
                if ((n = t.match(s.deprecatedTransitional))) return this.parser(`::ffff:${n[1]}`);
                if (s.native.test(t)) return a(t, 8);
                if ((n = t.match(s.transitional)) && ((p = n[6] || ""), (r = a(n[1].slice(0, -1) + p, 6)).parts)) {
                    for (o = [parseInt(n[2]), parseInt(n[3]), parseInt(n[4]), parseInt(n[5])], e = 0; e < o.length; e++)
                        if (!(0 <= (i = o[e]) && i <= 255)) return null;
                    return r.parts.push((o[0] << 8) | o[1]), r.parts.push((o[2] << 8) | o[3]), { parts: r.parts, zoneId: r.zoneId };
                }
                return null;
            }),
            (c.fromByteArray = function (t) {
                const r = t.length;
                if (4 === r) return new c.IPv4(t);
                if (16 === r) return new c.IPv6(t);
                throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address");
            }),
            (c.isValid = function (t) {
                return c.IPv6.isValid(t) || c.IPv4.isValid(t);
            }),
            (c.parse = function (t) {
                if (c.IPv6.isValid(t)) return c.IPv6.parse(t);
                if (c.IPv4.isValid(t)) return c.IPv4.parse(t);
                throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format");
            }),
            (c.parseCIDR = function (t) {
                try {
                    return c.IPv6.parseCIDR(t);
                } catch (r) {
                    try {
                        return c.IPv4.parseCIDR(t);
                    } catch (t) {
                        throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format");
                    }
                }
            }),
            (c.process = function (t) {
                const r = this.parse(t);
                return "ipv6" === r.kind() && r.isIPv4MappedAddress() ? r.toIPv4Address() : r;
            }),
            (c.subnetMatch = function (t, r, e) {
                let n, i, o, s;
                for (i in ((void 0 !== e && null !== e) || (e = "unicast"), r))
                    if (Object.prototype.hasOwnProperty.call(r, i))
                        for (!(o = r[i])[0] || o[0] instanceof Array || (o = [o]), n = 0; n < o.length; n++)
                            if (((s = o[n]), t.kind() === s[0].kind() && t.match.apply(t, s))) return i;
                return e;
            }),
            "undefined" != typeof module && module.exports ? (module.exports = c) : (t.ipaddr = c);
    })(this);
})("undefined" == typeof window ? (window = {}) : window);
// end ipaddr.js

function mbi_logTimestampToWikiTimestamp(logTimestamp) {
    const logDatetime = new Date(logTimestamp);
    return (
        logDatetime.toLocaleString("en-GB", {
            hour: "2-digit",
            minute: "2-digit",
        }) +
        ", " +
        logDatetime.toLocaleString("en-GB", {
            day: "numeric",
            month: "long",
            year: "numeric",
        })
    );
}

function mbi_getBlockOptionsString(blockEntry) {
    let retVal = " (";
    const flags = blockEntry.params.flags;
    if (flags.includes("nocreate")) {
        // ACB
        retVal += "account creation blocked,";
    }
    retVal += ")";
    retVal = retVal.replace(",)", ")").replace("()", "");
    return retVal;
}

function mbi_getBlockDisplayListItem(user, logid, timestamp, blockActor, blockComment, blockExpiry, blockOptions) {
    return `<li data-mw-logaction="block/block" class="mw-logline-block" > <a href="/w/index.php?title=Special:Log&amp;logid=${logid}" title="Special:Log" >${timestamp}</a > <a href="/wiki/User:${blockActor}" class="mw-userlink userlink" title="User:${blockActor}" ><bdi>${blockActor}</bdi></a > <span class="mw-usertoollinks mw-changeslist-links" ><span ><a href="/wiki/User_talk:${blockActor}" class="mw-usertoollinks-talk userlink" title="User talk:${blockActor}" >talk</a ></span > <span ><a href="/wiki/Special:Contributions/${blockActor}" class="mw-usertoollinks-contribs userlink" title="Special:Contributions/${blockActor}" >contribs</a ></span ></span > blocked <a href="/wiki/Special:Contributions/${user}" class="mw-userlink mw-anonuserlink" title="Special:Contributions/${user}" ><bdi>${user}</bdi></a > <span class="mw-usertoollinks mw-changeslist-links" ><span ><a href="/w/index.php?title=User_talk:${user}&amp;action=edit&amp;redlink=1" class="new mw-usertoollinks-talk" title="User talk:${user}" >talk</a ></span ></span > with an expiration time of <span class="blockExpiry" title="${blockExpiry}">${blockExpiry}</span> ${blockOptions} <span class="comment">(${blockComment})</span > </li>`;
}

function mbi_getWarningboxLocked(username, logid, timestamp, lockActor, lockComment) {
    return `<div class="warningbox mw-warning-with-logexcerpt mw-content-ltr more-block-info-box more-block-info-locked" dir="ltr" lang="en"> <p> This account is currently globally locked. The latest <span class="plainlinks" ><a class="external text" href="https://meta.wikimedia.org/wiki/Special:CentralAuth/${username}" >global account change log</a ></span > entry is provided below for reference: </p> <ul class="mw-logevent-loglines"> <li> <a href="https://meta.wikimedia.org/w/index.php?title=Special:Log&logid=${logid}" >${timestamp}</a > <a href="https://meta.wikimedia.org/wiki/User:${lockActor}" class="mw-userlink userlink" title="User:${lockActor}" ><bdi>${lockActor}</bdi></a > <span class="mw-usertoollinks mw-changeslist-links" ><span ><a href="https://meta.wikimedia.org/wiki/User_talk:${lockActor}" class="mw-usertoollinks-talk userlink" title="User talk:${lockActor}" >talk</a ></span > <span ><a href="https://meta.wikimedia.org/wiki/Special:Contributions/${lockActor}" class="mw-usertoollinks-contribs userlink" title="Special:Contributions/${lockActor}" >contribs</a ></span ></span > changed status for global account <a href="/wiki/User:${username}" class="mw-userlink userlink user-blocked-indef" ><bdi>${username}</bdi></a > <span class="mw-usertoollinks mw-changeslist-links" ><span ><a href="/wiki/User_talk:${username}" class="mw-usertoollinks-talk userlink user-blocked-indef" >talk</a ></span > <span ><a href="/wiki/Special:Contributions/${username}" class="mw-usertoollinks-contribs userlink user-blocked-indef" >contribs</a ></span ></span > : set locked; unset (none) <span class="comment">(${lockComment})</span> </li> </ul> <a href="https://meta.wikimedia.org/wiki/Special:CentralAuth/${username}">View full log</a> </div>`;
}

function mbi_HtmlEncode(rawString) {
    return $("<div>").text(rawString).html();
}

function mbi_insertMbiBox(boxContent) {
    let blockedNoticeElemList = $(".mw-contributions-blocked-notice");

    // If there's an existing block box, append after it
    // If not, just shove our box at the top of the content element
    if (blockedNoticeElemList.length > 0) {
        $($(".mw-contributions-blocked-notice")[0]).after(boxContent);
    } else {
        $("#mw-content-text").prepend(boxContent);
    }
}

async function mbi_getWarningboxRangeblocks(rangeblocks) {
    let liConcat = "";

    for (const cidr in rangeblocks) {
        const blockEntry = rangeblocks[cidr].blockEntries[0];
        const blockOptions = mbi_getBlockOptionsString(blockEntry);
        //const parsedComment = await mbi_parseWikitext(blockEntry.comment);
        liConcat += mbi_getBlockDisplayListItem(
            blockEntry.title.replace("User:", ""),
            blockEntry.logid,
            mbi_logTimestampToWikiTimestamp(blockEntry.timestamp),
            blockEntry.user,
            mbi_HtmlEncode(blockEntry.comment), // HTML-encode the block summary as they often contain hidden notes
            mbi_logTimestampToWikiTimestamp(blockEntry.params.expiry),
            blockOptions
        );
    }
    return `<div class="warningbox mw-warning-with-logexcerpt mw-content-ltr more-block-info-box more-block-info-rangeblocks " dir="ltr" lang="en" > <p> This IP address is currently subject to one or more rangeblocks. A list of these rangeblocks is below: </p> <ul class="mw-logevent-loglines">${liConcat}</ul> </div>`;
}

// Shamelessly nicked from [[User:GeneralNotability/mark-locked.js]]
async function mbi_isLocked(user) {
    const api = new mw.Api();
    try {
        const response = await api.get({
            action: "query",
            list: "globalallusers",
            agulimit: "1",
            agufrom: user,
            aguto: user,
            aguprop: "lockinfo",
        });
        if (response.query.globalallusers.length === 0) {
            // If the length is 0, then we couldn't find the global user
            return false;
        }
        // If the 'locked' field is present, then the user is locked
        return "locked" in response.query.globalallusers[0];
    } catch (error) {
        return false;
    }
}

async function mbi_parseWikitext(wikitext, stripParaTag = true) {
    const api = new mw.Api();
    const apiResponse = await api.post({
        action: "parse",
        format: "json",
        text: wikitext,
        prop: "text",
        disableeditsection: 1,
        formatversion: "2",
    });
    const parsedText = apiResponse.parse.text;
    if (stripParaTag) {
        let div = document.createElement("div");
        div.innerHTML = parsedText;
        const pHtml = div.getElementsByTagName("p")[0].innerHTML;
        return pHtml.trim();
    } else {
        return parsedText.trim();
    }
}

async function mbi_getRangeblocks(user) {}

async function mbi_getBlockEntries(user) {
    const now = Date.now();
    function blockIsCurrent(blockLogEntry) {
        return new Date(blockLogEntry.params.expiry) > now;
    }

    const retVal = {
        currentlyBlocked: false,
        everBlocked: false,
        blockEntries: [],
    };
    const api = new mw.Api();
    try {
        const response = await api.post({
            action: "query",
            list: "logevents",
            letitle: `User:${user}`,
            letype: "block",
        });
        const blockEntries = response.query.logevents;
        if (blockEntries.length > 0) {
            // user/range has been blocked at least once
            retVal.everBlocked = true;
            retVal.currentlyBlocked = blockIsCurrent(blockEntries[0]);
            retVal.blockEntries = blockEntries;
        }
        return retVal;
    } catch (error) {
        return undefined;
    }
}

async function mbi_getLockLogEntry(user) {
    const api = new mw.Api();
    try {
        const response = await $.post("https://meta.wikimedia.org/w/api.php", {
            action: "query",
            list: "logevents",
            lelimit: "1",
            letitle: `User:${user}@global`,
            leaction: "globalauth/setstatus",
            origin: "*",
            format: "json",
        });

        const logEvent = response.query.logevents[0];
        const wikiTimestamp = mbi_logTimestampToWikiTimestamp(logEvent.timestamp);
        const rawComment = logEvent.comment.replace("[[", "[[meta:"); // Crude fix for wikilinks in lock comments intending to point to Meta-Wiki pages
        const parsedComment = await mbi_parseWikitext(rawComment);
        const retVal = {
            logid: logEvent.logid,
            lockActor: logEvent.user,
            lockComment: parsedComment,
            timestamp: wikiTimestamp,
        };
        return retVal;
    } catch (error) {
        console.log(error);
        return undefined;
    }
}

$.when($.ready, mw.loader.using("mediawiki.util")).then(async function () {
    mw.util.addCSS("div.more-block-info-box { " + "border: 1px solid #7196bc; " + "background-color: #dbedff; " + "}");

    if (mw.config.get("wgPageName").includes("Special:Contributions/")) {
        const relevantUserName = mw.config.get("wgRelevantUserName");
        if (relevantUserName !== null) {
            // wgRelevantUserName is null for IP ranges (see phab T206954)
            const isip = mw.util.isIPAddress(relevantUserName, true);
            if (!isip) {
                // No point checking for locks if IP, IPs cannot be locked
                const userLocked = await mbi_isLocked(relevantUserName);
                if (userLocked) {
                    const lockLogEntry = await mbi_getLockLogEntry(relevantUserName);
                    const lockedBox = mbi_getWarningboxLocked(
                        relevantUserName,
                        lockLogEntry.logid,
                        lockLogEntry.timestamp,
                        lockLogEntry.lockActor,
                        lockLogEntry.lockComment
                    );
                    $("a[title='m:Global locks']").parent().remove(); // Remove "this account is globally locked" box
                    mbi_insertMbiBox(lockedBox); // Insert our info box
                }
            } else {
                // It's an IP, do rangeblock etc. checks
                const isipv6 = mw.util.isIPv6Address(relevantUserName, true);
                let rangeStart = 32;
                if (isipv6) {
                    range += 32; // Start at slash-64 for IPv6 (should this be 128...?)
                }

                function getNetworkAddress(addr) {
                    if (isipv6) {
                        //Hell knows at this point
                    } else {
                        return ipaddr.IPv4.networkAddressFromCIDR(addr).toString();
                    }
                }

                let ipRangeBlocks = {};
                let hasCurrentBlock = false;

                for (let i = rangeStart; i > 15; i--) {
                    const cidrRange = `${relevantUserName}/${i}`;
                    const networkAddr = `${getNetworkAddress(cidrRange)}/${i}`;
                    const blockEntries = await mbi_getBlockEntries(networkAddr);

                    if (blockEntries.everBlocked) {
                        hasCurrentBlock = Boolean(hasCurrentBlock | blockEntries.currentlyBlocked);
                        ipRangeBlocks[i] = blockEntries;
                    }
                }
                const rangeblockBox = await mbi_getWarningboxRangeblocks(ipRangeBlocks);
                console.log(rangeblockBox);
                mbi_insertMbiBox(rangeblockBox);
            }
        } else {
            // We're naively assuming an IP range
            console.log("IP RANGE YOU IDIOT");
        }
    }
});