User:Skmp/js/sampi tools.js

From Meta, a Wikimedia project coordination wiki
(Redirected from User:Skmp/ϡ)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/*
    Please don't use this script - it can be buggy and is not meant to be used by other people.
*/

mw.loader.using('jquery.ui', function() {
    const user_warnings_level_colors = {
        "1": "#0000ff44",
        "2": "#ffa50044",
        "3": "#ff550044",
        "4": "#ff000044",
        "4im" : "#00000044"
    }
    
    const user_warnings_config = {
        // Vandalism
        "uw-vandalism": {
            text: "⛔ Blatant vandalism",
			short_name: "Vandalism",
            options: ["1","2","3","4","4im"],
            background_color: "#ff000044"
        },
        "uw-subtle": {
            text: "❗❗ Subtle vandalism",
			short_name: "Vandalism",
            options: ["1","2","3","4"],
            background_color: "#ff000044"
        },
        "uw-disruptive": {
            text: "💥 Disruptive editing",
			short_name: "Disruptive editing",
            options: ["1","2","3",":uw-generic4"],
            background_color: "#ff000044"
        },

        // "Soft" vandalism
        "uw-joke": {
            text: "😜 Improper humor in articles",
			short_name: "Improper humor in articles",
            options: ["1","2","3","4", "4im"],
            background_color: "#ff550044"
        },

        // Destructive, but not necessarily vandalism
        "uw-delete": {
            text: "🪓 Removal of content without adequate explanation",
			short_name: "Removal of content",
            options: ["1","2","3","4","4im"],
            background_color: "#ff550044"
        },

        // Spam
        "uw-advert": {
            text: "📰 Using Wikipedia for advertising or promotion",
			short_name: "Advertising",
            options: ["1","2","3","4","4im"],
            background_color: "#ff550044"
        },
        "uw-spam": {
            text: "🔗 Adding spam links",
			short_name: "Spam",
            options: ["1","2","3","4","4im"],
            background_color: "#ff550044"
        },

        // BIO
        "uw-biog": {
            text: "🚫 Adding unreferenced controversial information about living persons",
			short_name: "Adding unreferenced controversial information",
            options: ["1","2","3","4", "4im"],
            background_color: "#ffa50044"
        },

        // Inexperienced users
        "uw-npov": {
            text: "🛑 Not adhering to neutral point of view",
			short_name: "Neutral point of view",
            options: ["1","2","3","4"],
            background_color: "#ffa50044"
        },
        
        "uw-test": {
            text: "🧪 Editing tests",
			short_name: "Test edits",
            options: ["1","2","3",":uw-vandalism4"],
            background_color: "#0000ff44"
        },
        
        "uw-talkinarticle": {
            text: "💬 Talking in articles",
			short_name: "Talking in articles",
            options: ["1","2","3",":uw-generic4"],
            background_color: "#0000ff44"
        },
    }
    
    const dialog_gsr = $(`\
<div id="sampi-dialog-gsr">
<fieldset style="display: flex; flex-direction: column; margin-top: 0" name="main-info">
    <legend>Request info</legend>
    <div><label for="request-text">Request text</label></div>
	<input class="request-text" name="request-text">
    <div>Common reasons: \
        <a href="javascript:sampi_set_gsr_text('requesting page deletion')">delete page</a>;
        <a href="javascript:sampi_set_gsr_text('requesting user/ip block - vandalism')">vandalism</a>
    </div>
</fieldset>
<pre class="final-request-text"></pre>
</div>
`)
	.dialog({
	    title: "New GS request",
	    closeOnEscape: true,
	    autoOpen: false,
	    resizable: false,
		width: 600,
	    dialogClass: "sampi-gsr-dialog-root",
	    buttons: [{
	    	text: "Send",
	    	click: function() {
		        gsr_send();
	    	}
	    }]
	});

    const dialog_uw = $(`\
<div id="sampi-dialog-uw">
<fieldset style="display: flex; flex-direction: column; margin-top: 0" name="main-info">
    <legend>Warning info</legend>
    <div><label for="warning-type">Warning type</label></div>
	<select class="warning-type" name="warning-type"></select>
    <div style="margin-top: 0.5em"><label for="warning-level">Warning level</label></div>
	<select class="warning-level" name="warning-level"></select>
	<div style="margin-top: 0.5em"><label for="additional-text">Additional text</label></div>
	<input class="additional-text" name="additional-text">
	<div>Common: \
        <a href="javascript:sampi_set_uw_text('#offer_help')">offer help</a>
    </div>
</fieldset>
<pre class="final-warning-wikitext"></pre>
</div>
`)
	.dialog({
	    title: "Leave user a warning",
	    closeOnEscape: true,
	    autoOpen: false,
	    resizable: false,
		width: 600,
	    dialogClass: "sampi-uw-dialog-root",
	    buttons: [{
	    	text: "Send",
	    	click: function() {
		        uw_send();
	    	}
	    }]
	});

    let inject_toolbox = function (scopes) {
        const toolbox_menu_html = `\
<nav class="mw-portlet vector-menu vector-menu-dropdown vector-menu-dropdown-noicon vectorMenu"><h3><span>&#993;</span></h3><div class="vector-menu-content"><ul class="vector-menu-content-list">
    ${ scopes.includes("user") ? `\
    <li><a id="sampi-toolbox-button-GSR" href="javascript:sampi_toolbox_gsr_click()"><span><s>GSR (WIP)</s></span></a></li>
    <li><a id="sampi-toolbox-button-UW" href="javascript:sampi_toolbox_uw_click()"><span>Warn user</span></a></li>
` : "" }
</ul></div></nav>
`;
        
        $('#p-search').before(toolbox_menu_html);
    }

    window.sampi_toolbox_gsr_click = function () {
        const final_request_text = $("#sampi-dialog-gsr > .final-request-text");
        final_request_text.text(`* ${ window.location.href } ~~` + "~~");

        document.querySelector("#sampi-dialog-gsr .request-text").oninput = function (e) {
            final_request_text.text(`* ${ window.location.href }${ e.target.value ? " " + e.target.value : "" } ~~` + "~~");
        };

        $("#sampi-dialog-gsr").dialog("open");
    }

    window.sampi_toolbox_uw_click = function () {
        const button = document.querySelector(".sampi-uw-dialog-root button");
        const button_text = button.getElementsByTagName("span")[0];
            
        const final_warning_wikitext = document.querySelector("#sampi-dialog-uw .final-warning-wikitext");
        const warning_type_selector = document.querySelector("#sampi-dialog-uw .warning-type");
        const warning_level_selector = document.querySelector("#sampi-dialog-uw .warning-level");

		const additional_text_input = document.querySelector("#sampi-dialog-uw .additional-text");
		
        for(const warning_type in user_warnings_config) {
            // TODO @performance this is not very efficient
            warning_type_selector.innerHTML += `<option value="${ warning_type }" style="background-color: ${ user_warnings_config[warning_type].background_color }">${  user_warnings_config[warning_type].text }</option>`
        }

        warning_type_selector.onchange = function (e) {
            warning_level_selector.innerHTML = "";
            
            const selected_type = e.target.value;

            if(!user_warnings_config[selected_type]) return;
            
            for(const warning_level of user_warnings_config[selected_type].options) {
                // TODO @performance this is not very efficient
                warning_level_selector.innerHTML += `<option value="${ warning_level }" style="background-color: ${ user_warnings_level_colors[warning_level] || "unset" }">${ warning_level[0] === ":" ? `{{${ warning_level.substring(1) }}}` : warning_level }</option>`
            }

            warning_level_selector.value = "-";
            final_warning_wikitext.innerText = "";
        }

        warning_level_selector.onchange = function (e) {
            if(warning_level_selector.value[0] === ":") {
                final_warning_wikitext.dataset.template = warning_level_selector.value.substring(1);
				final_warning_wikitext.dataset.short_name = "";
            } else {
                final_warning_wikitext.dataset.template = warning_type_selector.value + warning_level_selector.value;
				final_warning_wikitext.dataset.short_name = user_warnings_config[warning_type_selector.value].short_name;
            }

            final_warning_wikitext.innerText = "{{sub" + `st:${ final_warning_wikitext.dataset.template }}}${ additional_text_input.value ? `
:${ additional_text_input.value }` : "" } ~~` + `~~`;

            button_text.innerText = "Send";
            button.style.pointerEvents = "all";
        }

		additional_text_input.oninput = function (e) {
			if(!final_warning_wikitext.dataset.template) return;
			
            final_warning_wikitext.innerText = "{{sub" + `st:${ final_warning_wikitext.dataset.template }}}${ additional_text_input.value ? `
:${ additional_text_input.value }` : "" } ~~` + `~~`;
        };

        warning_type_selector.value = "-";
        
        $("#sampi-dialog-uw").dialog("open");
    }

    window.sampi_set_gsr_text = function (text) {
		const request_text_input =  document.querySelector("#sampi-dialog-gsr .request-text");
		
        request_text_input.value = text;
		request_text_input.dispatchEvent(new Event('input', { bubbles: true, value: text }));
    }

	window.sampi_set_uw_text = function (text) {
		const additional_text_input = document.querySelector("#sampi-dialog-uw .additional-text");
		let final_text = text;
		
		if(text === "#offer_help") {
			final_text = `If you have questions or need help, feel free to leave me a message on this page, or on [[${ mw.config.get("wgFormattedNamespaces")[3] }:${ mw.config.get("wgUserName") }|my talk page]].`;
		}
		
        additional_text_input.value = final_text;
		additional_text_input.dispatchEvent(new Event('input', { bubbles: true, value: final_text }));
    }
    
    let get_csrf_token = function (cb) {
        fetch(`/w/api.php?action=query&format=json&meta=tokens`, {
            method: "GET"
        })
        .then(response => {
            response.json()
            .then(json => {
                if(json && json.query && json.query.tokens && json.query.tokens.csrftoken) {
                    cb(json.query.tokens.csrftoken);
                }
            })
        });
    }

    const gsr_send = function() {
        const button = document.querySelector(".sampi-gsr-dialog-root button");
        const button_text = button.getElementsByTagName("span")[0];
            
        button_text.innerText = "Sending...";
        button.style.pointerEvents = "none";

        // Send the request
        const api = new mw.ForeignApi("https://meta.wikimedia.org/w/api.php");

        api.postWithToken("csrf", {
            action: "edit",
            title: "User:Skmp/Sandbox",
            summary: "Requesting action from global sysops",
            appendtext: `
${ $("#sampi-dialog-gsr > .final-request-text").text() }`,
            nocreate: 1,
        })
        .then(json => {
            if(json.edit && json.edit.result === "Success") {
                button_text.innerText = "Sent!";
            } else {
                if(json.error) alert(json.error.info);
                button_text.innerText = "Error!";

                console.error("[Sampi error]", json.error);
            }
        });
    }

    const uw_send = function() {
        const final_warning_wikitext = document.querySelector("#sampi-dialog-uw .final-warning-wikitext");
        
        const button = document.querySelector(".sampi-uw-dialog-root button");
        const button_text = button.getElementsByTagName("span")[0];
		
        if(!final_warning_wikitext.dataset.template) {
            button_text.innerText = "Please, select a template";
            return;
        }
        
        button_text.innerText = "Checking the template...";
        button.style.pointerEvents = "none";
        
        const api = new mw.Api();

        // Check if selected template exists on this wiki
        api.get({
            action: "query",
            titles: `Template:${ final_warning_wikitext.dataset.template }`,
        })
        .then(json => {
            if(json.query.pages["-1"]) {
                button_text.innerText = "This wiki does not have such template";
                return;
            }

            button_text.innerText = "Saving the talk page...";

            // TODO @cleanup @refactor this isn't the best approach
            const date_str = new Date().toGMTString().split(" ", 4).join(" ");
			const section_title = `${ date_str }${ final_warning_wikitext.dataset.short_name ? `: ${ final_warning_wikitext.dataset.short_name }` : "" }`;

			const watch_date = new Date();
			watch_date.setDate(watch_date.getDate() + 5);
			
            api.postWithToken('csrf', {
                action: "edit",
            	title: `${ mw.config.get("wgFormattedNamespaces")[3] }:${ mw.config.get("wgRelevantUserName") }`,
            	section: "new",
            	sectiontitle: section_title,
            	summary: `+${ final_warning_wikitext.dataset.template }; Leaving user a warning ([[:meta:User:Skmp/ϡ|ϡ]])`,
            	appendtext: final_warning_wikitext.innerText,
				watchlist: "watch",
				watchlistexpiry: watch_date.toISOString()
            })
            .then(json => {
                if(json.edit && json.edit.result === "Success") {
                    button_text.innerText = "Sent!";

					// If the "talk" link is red - make it blue, we have just created a talk page for a user
					const talk_link_el = document.querySelector(".mw-changeslist-links > span > a");

					if(talk_link_el.classList.contains("new")) {
						const url = new URLSearchParams(talk_link_el.href);
						url.delete("action");
						url.delete("redlink");
						talk_link_el.href = decodeURIComponent(url.toString());

						talk_link_el.className = "";
						talk_link_el.style.backgroundColor = "#ffd700";
					}
                } else {
                    if(json.error) alert(json.error.info);
                    button_text.innerText = "Error!";
    
                    console.error("[Sampi error]", json.error);
                }
            });
        });
    }
    
    let append_revision_links = function (pagehistory_el, is_page_history) {
        const revision_els = pagehistory_el.children;

        // Go through all the revisions on the page
        for(const revision_el of revision_els) {
            const sampi_el = document.createElement("span");
            sampi_el.className = "mw-changeslist-links";
            sampi_el.style = "border: 1px dashed #00bfff; background-color: rgb(0 191 255 / 15%); padding: 0 0.2em; margin-left: 0.2em";

            const sampi_text_el = document.createElement("span");
            sampi_text_el.innerHTML = "&#993;";
            sampi_text_el.style = "font-family: monospace";

            const sampi_undo_btn_el = document.createElement("div");
            sampi_undo_btn_el.className = "sampi-action-button";
            sampi_undo_btn_el.style = "color: #0645ad; cursor: pointer; display: inline-block; margin-left: 0.25em";
            sampi_undo_btn_el.innerHTML = "&#10554; undo";
            sampi_undo_btn_el.title = "Undo this revision";

            sampi_el.appendChild(sampi_text_el);
            sampi_el.appendChild(sampi_undo_btn_el);

            // Undo revision click
            sampi_undo_btn_el.addEventListener("click", e => {
                // Require a summary to perform the undo
                let custom_reason = prompt();

                if(!custom_reason) {
                    alert("Action was not performed");
                    return;
                };

                sampi_undo_btn_el.style.pointerEvents = "none";
                sampi_undo_btn_el.style.color = "gray";
                sampi_undo_btn_el.innerText = "1/3";

                let target_revid_author_username;
                let target_page_id;
                const target_revid = revision_el.dataset.mwRevid;

                // Get info about the target revision
                fetch(`/w/api.php?action=query&format=json&prop=revisions&revids=${ target_revid }&rvprop=user`, {
                    method: "GET"
                })
                .then(response => {
                    response.json()
                    .then(selected_rev_json => {
                        sampi_undo_btn_el.innerText = "2/3";

                        if(selected_rev_json && selected_rev_json.query && selected_rev_json.query.pages) {
                            for(const selected_rev_page_id in selected_rev_json.query.pages) {
                                target_revid_author_username = selected_rev_json.query.pages[selected_rev_page_id].revisions[0].user;
                                target_page_id = selected_rev_json.query.pages[selected_rev_page_id].pageid;
                            }
                        }

                        if(!target_revid_author_username || !target_page_id) {
                            alert("Undo error. Could not get the author of the target revision or the target page");
                            return;
                        }

                        get_csrf_token(function (csrf_token) {
                            sampi_undo_btn_el.innerText = "3/3";

                            // Perform the undo
                            fetch(`/w/api.php?action=edit&format=json`, {
                                method: "POST",
                                body: new URLSearchParams({
                                    pageid: target_page_id,
                                    minor: 1,
                                    nocreate: 1,
                                    token: csrf_token,
                                    undo: target_revid,
                                    summary: `Undid revision ${ target_revid } by [[Special:Contributions/${ target_revid_author_username }|${ target_revid_author_username }]]; ${ custom_reason } ([[:meta:User:Skmp/ϡ|ϡ]])`,
                                })
                            })
                            .then(response => {
                                response.json()
                                .then(undo_json => {
                                    if(undo_json.edit && undo_json.edit.result === "Success") {
                                       sampi_undo_btn_el.style.color = "green";
                                       sampi_undo_btn_el.style.fontWeight = "bold";
                                       sampi_undo_btn_el.innerText = "done";
                                    } else {
                                       sampi_undo_btn_el.style.color = "crimson";
                                       sampi_undo_btn_el.style.fontWeight = "bold";
                                       sampi_undo_btn_el.innerText = undo_json.error ? undo_json.error.info : "error!";

                                       console.error("[Sampi error]", undo_json.edit);
                                    }
                                })
                            });
                        });
                    });
                });
            });

            // Revert to this revision
            if(is_page_history) {
                const sampi_restore_btn_el = document.createElement("div");
                sampi_restore_btn_el.className = "sampi-action-button";
                sampi_restore_btn_el.style = "color: #0645ad; cursor: pointer; display: inline-block; margin-left: 0.25em";
                sampi_restore_btn_el.innerHTML = "&#8610; restore";
                sampi_restore_btn_el.title = "Restore page to this revision";

                sampi_el.appendChild(sampi_restore_btn_el);

                sampi_restore_btn_el.addEventListener("click", e => {
                    let custom_reason = false;

                    // ctrl down - skip summary
                    if(!e.ctrlKey) {
                        custom_reason = prompt();

                        if(!custom_reason) {
                            alert("Action was not performed");
                            return;
                        }
                    }

                    sampi_restore_btn_el.style.pointerEvents = "none";
                    sampi_restore_btn_el.style.color = "gray";
                    sampi_restore_btn_el.innerText = "1/4";

                    let newest_revid;
                    let target_revid_author_username;
                    let target_page_id;
                    const target_revid = revision_el.dataset.mwRevid;

                    // Get info about the selected revision
                    fetch(`/w/api.php?action=query&format=json&prop=revisions&revids=${ target_revid }&rvprop=user`, {
                        method: "GET"
                    })
                    .then(response => {
                        response.json()
                        .then(selected_rev_json => {
                            sampi_restore_btn_el.innerText = "2/4";

                            if(selected_rev_json && selected_rev_json.query && selected_rev_json.query.pages) {
                                for(const selected_rev_page_id in selected_rev_json.query.pages) {
                                    target_revid_author_username = selected_rev_json.query.pages[selected_rev_page_id].revisions[0].user;
                                    target_page_id = selected_rev_json.query.pages[selected_rev_page_id].pageid;
                                }
                            }

                            if(!target_revid_author_username) {
                                alert("Restore error. Could not get the author of the target revision");
                                return;
                            }

                            // Get the last revision of this page
                            fetch(`/w/api.php?action=query&format=json&prop=revisions&pageids=${ target_page_id }&rvlimit=1&rvdir=older`, {
                                method: "GET"
                            })
                            .then(response => {
                                response.json()
                                .then(last_rev_json => {
                                    sampi_restore_btn_el.innerText = "3/4";

                                    if(last_rev_json && last_rev_json.query && last_rev_json.query.pages) {
                                        for(const last_rev_page_id in last_rev_json.query.pages) {
                                            newest_revid = last_rev_json.query.pages[last_rev_page_id].revisions[0].revid;

                                            if(!newest_revid || !target_revid || !target_page_id) {
                                                alert("Error reverting to a revision. Some id is not known!");
                                                return;
                                            }                

                                            // Get a csrf token
                                            get_csrf_token(function (csrf_token) {
                                                sampi_restore_btn_el.innerText = "4/4";

                                                // Perform the revert
                                                fetch(`/w/api.php?action=edit&format=json`, {
                                                    method: "POST",
                                                    body: new URLSearchParams({
                                                        pageid: target_page_id,
                                                        minor: 1,
                                                        nocreate: 1,
                                                        summary: `Reverted page to revision ${ target_revid } by [[Special:Contributions/${ target_revid_author_username }|${ target_revid_author_username }]]${ custom_reason ? `; ${ custom_reason }` : "" } ([[:meta:User:Skmp/ϡ|ϡ]])`,
                                                        token: csrf_token,
                                                        undo: newest_revid,
                                                        undoafter: target_revid
                                                    })
                                                })
                                                .then(response => {
                                                    response.json()
                                                    .then(revert_json => {
                                                        if(revert_json.edit && revert_json.edit.result === "Success") {
                                                            sampi_restore_btn_el.style.color = "green";
                                                            sampi_restore_btn_el.style.fontWeight = "bold";
                                                            sampi_restore_btn_el.innerText = "done, reloading...";

                                                            window.location.reload();
                                                        } else {
                                                           sampi_restore_btn_el.style.color = "crimson";
                                                           sampi_restore_btn_el.style.fontWeight = "bold";
                                                           sampi_restore_btn_el.innerText = revert_json.error ? revert_json.error.info : "error!";;

                                                           console.error("[Sampi error]", revert_json.edit);
                                                        }
                                                    })
                                                });
                                            })
                                        }
                                    }
                                })
                            });
                        })
                    });

                    return false;
                });
            }

            // Check if this revision can be rolled back
            const rollback_link = revision_el.querySelector(".mw-rollback-link > a");

            if(rollback_link) {
                // Add rollback links
                const sampi_rollback_btn_el = document.createElement("div");
                sampi_rollback_btn_el.className = "sampi-action-button";
                sampi_rollback_btn_el.style = "color: #0645ad; cursor: pointer; display: inline-block; margin-left: 0.25em";
                sampi_rollback_btn_el.innerHTML = "&#10539; rollback";
                sampi_rollback_btn_el.title = "Rollback edits by this user";

                sampi_el.appendChild(sampi_rollback_btn_el);

                // On rollback link click
                sampi_rollback_btn_el.addEventListener("click", e => {
                    // Require ctrl key to perform a rollback
                	if(!e.ctrlKey) {
                		sampi_rollback_btn_el.innerHTML += " (hold ctrl!)";
                		return;
                	}
                	
                    sampi_rollback_btn_el.style.pointerEvents = "none";
                    sampi_rollback_btn_el.style.color = "gray";
                    sampi_rollback_btn_el.innerText = "...";

                    // Get the revision info from the mw's rollback link
                    const rollback_link_split = rollback_link.href.split("?", 2);
                    const rollback_link_params = new URLSearchParams(rollback_link_split[1]);
                    
                    // Get the url params from the link
                    const token = rollback_link_params.get("token");
                    const title = rollback_link_params.get("title");
                    const user = rollback_link_params.get("from");

                    if(!token || !title || !user) {
                        alert("Rollback error. Could not get all the necessary information!");
                    } else {
                        // Perform a rollback
                        fetch(`/w/api.php?action=rollback&format=json`, {
                            method: "POST",
                            body: new URLSearchParams({
                                title,
                                user,
                                token
                            })
                        })
                        .then(response => {
                            response.json()
                            .then(json => {
                                if(json && json.rollback && json.rollback.revid) {
                                    sampi_rollback_btn_el.style.color = "green";
                                    sampi_rollback_btn_el.style.fontWeight = "bold";
                                    sampi_rollback_btn_el.innerText = "done";
                                }
                            })
                        });
                    }

                    return false;
                });
            }

            revision_el.appendChild(sampi_el);
        }
    }

    let append_useful_contribs_links = function() {
        const tools_el = document.getElementsByClassName("mw-contributions-user-tools")[0];
        const username = mw.config.get('wgPageName').split("/", 2)[1];

        tools_el.innerHTML += ` \
<span class="mw-changeslist-links">
    <span>&#993;</span>
    <span><a target="_blank" href="https://guc.toolforge.org/?user=${ username }&blocks=true">GUC</a></span>
</span>`
    }

    // Script start
    let pagehistory_el = document.getElementById("pagehistory");
    let contributionslist_el = document.getElementsByClassName("mw-contributions-list")[0];

    const is_contribs_page = mw.config.get("wgCanonicalSpecialPageName") === "Contributions";
    const is_user_talk_page = mw.config.get("wgCanonicalNamespace") === "User_talk";
    
    let toolbox_scopes = [];
    
    // TODO @cleanup @refator use mw.config.get('wgCanonicalSpecialPageName'), mw.config.get('wgNamespaceNumber'), etc. instead
    
    // If this page has a history or contributions in it - append revision-related links
    if(pagehistory_el || contributionslist_el) {
        append_revision_links(pagehistory_el || contributionslist_el, pagehistory_el && true);
    }
    
    if(is_contribs_page) {
        // Contribs page
        append_useful_contribs_links();
        toolbox_scopes = toolbox_scopes.concat(["user", "contribs"]);
    }

    if(is_user_talk_page) {
        toolbox_scopes.push("user");
    }

    inject_toolbox(toolbox_scopes);
});