User:DeltaQuadBot/sciptcopytest.js

/* eslint-disable es/no-object-entries */

/* eslint-disable no-restricted-syntax */

//

// @ts-check

// GeneralNotability's rewrite of Tim's SPI helper script

// v2.3.0 "Editing Under the Influence"

// Adapted from User:Mr.Z-man/closeAFD

importStylesheet('User:GeneralNotability/spihelper.css' );

importScript('User:Timotheus Canens/displaymessage.js');

// Typedefs

/**

* @typedef SelectOption

* @type {Object}

* @property {string} label Text to display in the drop-down

* @property {string} value Value to return if this option is selected

* @property {boolean} selected Whether this item should be selected by default

* @property {boolean=} disabled Whether this item should be disabled

*/

/**

* @typedef BlockEntry

* @type {Object}

* @property {string} username Username to block

* @property {string} duration Duration of block

* @property {boolean} acb If set, account creation is blocked

* @property {boolean} ab Whether autoblock is enabled (for registered users)/

* logged-in users are blocked (for IPs)

* @property {boolean} ntp If set, talk page access is blocked

* @property {boolean} nem If set, email access is blocked

* @property {string} tpn Type of talk page notice to apply on block

*/

/**

* @typedef TagEntry

* @type {Object}

* @property {string} username Username to tag

* @property {string} tag Tag to apply to user

* @property {string} altmasterTag Altmaster tag to apply to user, if relevant

* @property {boolean} blocking Whether this account is marked for block as well

*/

// Globals

// User-configurable settings, these are the defaults but will be updated by

// spiHelper_loadSettings()

const spiHelper_settings = {

// Choices are 'watch' (unconditionally add to watchlist), 'preferences'

// (follow default preferences), 'nochange' (don't change the watchlist

// status of the page), and 'unwatch' (unconditionally remove)

watchCase: 'preferences',

watchCaseExpiry: 'indefinite',

watchArchive: 'nochange',

watchArchiveExpiry: 'indefinite',

watchTaggedUser: 'preferences',

watchTaggedUserExpiry: 'indefinite',

watchNewCats: 'nochange',

watchNewCatsExpiry: 'indefinite',

watchBlockedUser: true,

watchBlockedUserExpiry: 'indefinite',

// Lets people disable clerk options if they're not a clerk

clerk: true,

// Log all actions to Special:MyPage/spihelper_log

log: false,

// Enable the "move section" button

iUnderstandSectionMoves: false,

// These are for debugging to view as other roles. If you're picking apart the code and

// decide to set these (especially the CU option), it is YOUR responsibility to make sure

// you don't do something that violates policy

debugForceCheckuserState: null,

debugForceAdminState: null

};

/** @type {string} Name of the SPI page in wiki title form

* (e.g. Wikipedia:Sockpuppet investigations/Test) */

let spiHelper_pageName = mw.config.get('wgPageName').replace(/_/g, ' ');

/** @type {number} The main page's ID - used to check if the page

* has been edited since we opened it to prevent edit conflicts

*/

let spiHelper_startingRevID = mw.config.get('wgCurRevisionId');

// Just the username part of the case

let spiHelper_caseName = spiHelper_pageName.replace(/Wikipedia:Sockpuppet investigations\//g, '');

/** list of section IDs + names corresponding to separate investigations */

let spiHelper_caseSections = [];

/** @type {?number} Selected section, "null" means that we're opearting on the entire page */

let spiHelper_sectionId = null;

/** @type {?string} Selected section's name (e.g. "10 June 2020") */

let spiHelper_sectionName = null;

/** Map of top-level actions the user has selected */

const spiHelper_ActionsSelected = {

Case_act: false,

Block: false,

Note: false,

Close: false,

Rename: false,

Archive: false

};

/** @type {BlockEntry[]} Requested blocks */

const spiHelper_blocks = [];

/** @type {TagEntry[]} Requested tags */

const spiHelper_tags = [];

/** @type {string[]} Requested global locks */

const spiHelper_globalLocks = [];

// Count of unique users in the case (anything with a checkuser, checkip, user, ip, or vandal template on the page)

let spiHelper_usercount = 0;

const spiHelper_SECTION_RE = /^(?:===[^=]*===|=====[^=]*=====)\s*$/m;

/** @type {SelectOption[]} List of possible selections for tagging a user in the block/tag interface

*/

const spiHelper_TAG_OPTIONS = [

{ label: 'None', selected: true, value: '' },

{ label: 'Suspected sock', value: 'blocked', selected: false },

{ label: 'Proven sock', value: 'proven', selected: false },

{ label: 'CU confirmed sock', value: 'confirmed', selected: false },

{ label: 'Blocked master', value: 'master', selected: false },

{ label: 'CU confirmed master', value: 'sockmasterchecked', selected: false },

{ label: '3X banned master', value: 'bannedmaster', selected: false }

];

/** @type {SelectOption[]} List of possible selections for tagging a user's altmaster in the block/tag interface */

const spiHelper_ALTMASTER_TAG_OPTIONS = [

{ label: 'None', selected: true, value: '' },

{ label: 'Suspected alt master', value: 'suspected', selected: false },

{ label: 'Proven alt master', value: 'proven', selected: false }

];

/** @type {SelectOption[]} List of templates that CUs might insert */

const spiHelper_CU_TEMPLATES = [

{ label: 'CU templates', selected: true, value: '', disabled: true },

{ label: 'Confirmed', selected: false, value: '{{confirmed}}' },

{ label: 'Confirmed/No Comment', selected: false, value: '{{confirmed-nc}}' },

{ label: 'Indistinguishable', selected: false, value: '{{tallyho}}' },

{ label: 'Likely', selected: false, value: '{{likely}}' },

{ label: 'Possilikely', selected: false, value: '{{possilikely}}' },

{ label: 'Possible', selected: false, value: '{{possible}}' },

{ label: 'Unlikely', selected: false, value: '{{unlikely}}' },

{ label: 'Unrelated', selected: false, value: '{{unrelated}}' },

{ label: 'Inconclusive', selected: false, value: '{{inconclusive}}' },

{ label: 'Need behavioral eval', selected: false, value: '{{behav}}' },

{ label: 'No sleepers', selected: false, value: '{{nosleepers}}' },

{ label: 'Stale', selected: false, value: '{{IPstale}}' },

{ label: 'No comment (IP)', selected: false, value: '{{ncip}}'}

];

/** @type {SelectOption[]} Templates that a clerk or admin might insert */

const spiHelper_ADMIN_TEMPLATES = [

{ label: 'Admin/clerk templates', selected: true, value: '', disabled: true },

{ label: 'Duck', selected: false, value: '{{duck}}' },

{ label: 'Megaphone Duck', selected: false, value: '{{megaphone duck}}' },

{ label: 'Blocked and tagged', selected: false, value: '{{bnt}}' },

{ label: 'Blocked, no tags', selected: false, value: '{{bwt}}' },

{ label: 'Blocked, awaiting tags', selected: false, value: '{{sblock}}' },

{ label: 'Blocked, tagged, closed', selected: false, value: '{{btc}}' },

{ label: 'Diffs needed', selected: false, value: '{{DiffsNeeded|moreinfo}}' },

{ label: 'Locks requested', selected: false, value: '{{GlobalLocksRequested}}' }

];

// Regex to match the case status, group 1 is the actual status

const spiHelper_CASESTATUS_RE = /{{\s*SPI case status\s*\|?\s*(\S*?)\s*}}/i;

// Regex to match closed case statuses (close or closed)

const spiHelper_CASESTATUS_CLOSED_RE = /^closed?$/i;

const spiHelper_CLERKSTATUS_RE = /{{(CURequest|awaitingadmin|clerk ?request|(?:self|requestand|cu)?endorse|inprogress|decline(?:-ip)?|moreinfo|relisted|onhold)}}/i;

const spiHelper_SOCK_SECTION_RE_WITH_NEWLINE = /====\s*Suspected sockpuppets\s*====\n*/i;

const spiHelper_ADMIN_SECTION_RE = /\s*====\s*Clerk, CheckUser, and\/or patrolling admin comments<\/big>\s*====\s*/i;

const spiHelper_CU_BLOCK_RE = /{{(checkuserblock(-account|-wide)?|checkuser block)}}/i;

const spiHelper_ARCHIVENOTICE_RE = /{{\s*SPI\s*archive notice\|.*}}/i;

const spiHelper_PRIORCASES_RE = /{{spipriorcases}}/i;

// regex to remove hidden characters from form inputs - they mess up some things,

// especially mw.util.isIP

const spiHelper_HIDDEN_CHAR_NORM_RE = /\u200E/;

const spihelper_ADVERT = ' (using spihelper.js)';

// The current wiki's interwiki prefix

const spiHelper_interwikiPrefix = spiHelper_getInterwikiPrefix();

// Actually put the portlets in place if needed

if (mw.config.get('wgPageName').includes('Wikipedia:Sockpuppet_investigations/') &&

!mw.config.get('wgPageName').includes('Wikipedia:Sockpuppet_investigations/SPI/') &&

!mw.config.get('wgPageName').includes('/Archive')) {

mw.loader.load('mediawiki.user');

$(spiHelper_addLink);

}

// Main functions - do the meat of the processing and UI work

const spiHelper_TOP_VIEW = `

Handling SPI case

  • `;

    /**

    * Initialization functions for spiHelper, displays the top-level menu

    */

    async function spiHelper_init() {

    'use strict';

    spiHelper_caseSections = await spiHelper_getInvestigationSectionIDs();

    // First, insert the template text

    displayMessage(spiHelper_TOP_VIEW);

    // Narrow search scope

    const $topView = $('#spiHelper_topViewDiv', document);

    // Next, modify what's displayed

    // Set the block selection label based on whether or not the user is an admin

    $('#spiHelper_blockLabel', $topView).text(spiHelper_isAdmin() ? 'Block/tag socks' : 'Tag socks');

    // Wire up a couple of onclick handlers

    $('#spiHelper_Move', $topView).on('click', function () {

    spiHelper_updateArchive();

    });

    $('#spiHelper_Archive', $topView).on('click', function () {

    spiHelper_updateMove();

    });

    // Generate the section selector

    const $sectionSelect = $('#spiHelper_sectionSelect', $topView);

    $sectionSelect.on('change', () => {

    spiHelper_setCheckboxesBySection();

    });

    // Add the dates to the selector

    for (let i = 0; i < spiHelper_caseSections.length; i++) {

    const s = spiHelper_caseSections[i];

    $('

    }

    // All-sections selector...deliberately at the bottom, the default should be the first section

    $('

    // Hide block and close from non-admin non-clerks

    if (!(spiHelper_isAdmin() || spiHelper_isClerk())) {

    $('.spiHelper_adminClerkClass', $topView).hide();

    }

    // Hide move and archive from non-clerks

    if (!spiHelper_isClerk()) {

    $('.spiHelper_clerkClass', $topView).hide();

    }

    // Set the checkboxes to their default states

    spiHelper_setCheckboxesBySection();

    }

    const spiHelper_ACTION_VIEW = `

    Back to top menu


    Handling SPI case

    Changing case status

  • Suppress the usual block summary and only use {{checkuserblock-account}} and {{checkuserblock}} (no effect if "mark blocks as CU blocks" is not checked).

  • Username Blk? Duration ACB AB/AO NTP NEM Tag Alt Master Req Lock?
    (All users)

    Marking case as closed

    Move section

    Archiving case

    Comments

    `;

    /**

    * Big function to generate the SPI form from the top-level menu selections

    */

    async function spiHelper_generateForm() {

    'use strict';

    spiHelper_usercount = 0;

    const $topView = $('#spiHelper_topViewDiv', document);

    spiHelper_ActionsSelected.Case_act = $('#spiHelper_Case_Action', $topView).prop('checked');

    spiHelper_ActionsSelected.Block = $('#spiHelper_BlockTag', $topView).prop('checked');

    spiHelper_ActionsSelected.Note = $('#spiHelper_Comment', $topView).prop('checked');

    spiHelper_ActionsSelected.Close = $('#spiHelper_Close', $topView).prop('checked');

    spiHelper_ActionsSelected.Rename = $('#spiHelper_Move', $topView).prop('checked');

    spiHelper_ActionsSelected.Archive = $('#spiHelper_Archive', $topView).prop('checked');

    const pagetext = await spiHelper_getPageText(spiHelper_pageName, false, spiHelper_sectionId);

    if (!(spiHelper_ActionsSelected.Case_act ||

    spiHelper_ActionsSelected.Note || spiHelper_ActionsSelected.Close ||

    spiHelper_ActionsSelected.Archive || spiHelper_ActionsSelected.Block ||

    spiHelper_ActionsSelected.Rename)) {

    displayMessage('');

    return;

    }

    displayMessage(spiHelper_ACTION_VIEW);

    // Reduce the scope that jquery operates on

    const $actionView = $('#spiHelper_actionViewDiv', document);

    // Wire up the action view

    $('#spiHelper_backLink', $actionView).on('click', () => {

    spiHelper_init();

    });

    if (spiHelper_ActionsSelected.Case_act) {

    const result = spiHelper_CASESTATUS_RE.exec(pagetext);

    let casestatus = '';

    if (result) {

    casestatus = result[1];

    }

    const canAddCURequest = (casestatus === '' || /^(?:admin|moreinfo|cumoreinfo|hold|cuhold|clerk|open)$/i.test(casestatus));

    const cuRequested = /^(?:CU|checkuser|CUrequest|request|cumoreinfo)$/i.test(casestatus);

    const cuCompleted = /^(?:inprogress|checking|relist(ed)?|checked|completed|declined?|cudeclin(ed)?)$/i.test(casestatus);

    /** @type {SelectOption[]} Generated array of values for the case status select box */

    const selectOpts = [

    { label: 'No action', value: 'noaction', selected: true }

    ];

    if (spiHelper_CASESTATUS_CLOSED_RE.test(casestatus)) {

    selectOpts.push({ label: 'Reopen', value: 'open', selected: false });

    }

    if (spiHelper_isCheckuser()) {

    selectOpts.push({ label: 'Mark as in progress', value: 'inprogress', selected: false });

    }

    if (spiHelper_isClerk() || spiHelper_isAdmin()) {

    selectOpts.push({ label: 'Request more information', value: 'moreinfo', selected: false });

    }

    if (canAddCURequest) {

    // Statuses only available if the case could be moved to "CU requested"

    selectOpts.push({ label: 'Request CU', value: 'CUrequest', selected: false });

    if (spiHelper_isClerk()) {

    selectOpts.push({ label: 'Request CU and self-endorse', value: 'selfendorse', selected: false });

    }

    }

    // CU already requested

    if (cuRequested && spiHelper_isClerk()) {

    // Statuses only available if CU has been requested, only clerks + CUs should use these

    selectOpts.push({ label: 'Endorse for CU attention', value: 'endorse', selected: false });

    // Switch the decline option depending on whether the user is a checkuser

    if (spiHelper_isCheckuser()) {

    selectOpts.push({ label: 'Endorse CU as a CheckUser', value: 'cuendorse', selected: false });

    }

    if (spiHelper_isCheckuser()) {

    selectOpts.push({ label: 'Decline CU', value: 'cudecline', selected: false });

    }

    else {

    selectOpts.push({ label: 'Decline CU', value: 'decline', selected: false });

    }

    selectOpts.push({ label: 'Request more information for CU', value: 'cumoreinfo', selected: false });

    }

    // This is mostly a CU function, but let's let clerks and admins set it

    // in case the CU forgot (or in case we're un-closing))

    if (spiHelper_isAdmin() || spiHelper_isClerk()) {

    selectOpts.push({ label: 'Mark as checked', value: 'checked', selected: false });

    }

    if (spiHelper_isClerk() && cuCompleted) {

    selectOpts.push({ label: 'Relist for another check', value: 'relist', selected: false });

    }

    if (spiHelper_isCheckuser()) {

    selectOpts.push({ label: 'Place case on CU hold', value: 'cuhold', selected: false });

    } else { // I guess it's okay for anyone to have this option

    selectOpts.push({ label: 'Place case on hold', value: 'hold', selected: false });

    }

    selectOpts.push({ label: 'Request clerk action', value: 'clerk', selected: false });

    // I think this is only useful for non-admin clerks to ask admins to do stuff

    if (!spiHelper_isAdmin() && spiHelper_isClerk()) {

    selectOpts.push({ label: 'Request admin action', value: 'admin', selected: false });

    }

    // Generate the case action options

    spiHelper_generateSelect('spiHelper_CaseAction', selectOpts);

    // Add the onclick handler to the drop-down

    $('#spiHelper_CaseAction', $actionView).on('change', function (e) {

    spiHelper_caseActionUpdated($(e.target));

    });

    } else {

    $('#spiHelper_actionView', $actionView).hide();

    }

    if (spiHelper_ActionsSelected.Block) {

    if (spiHelper_isAdmin()) {

    $('#spiHelper_blockTagHeader', $actionView).text('Blocking and tagging socks');

    } else {

    $('#spiHelper_blockTagHeader', $actionView).text('Tagging socks');

    }

    const checkuser_re = /{{\s*check(?:user|ip)\s*\|\s*(?:1=)?\s*([^\|}]*?)\s*(?:\|master name\s*=\s*.*)?}}/gi;

    const results = pagetext.match(checkuser_re);

    const likelyusers = [];

    const likelyips = [];

    const possibleusers = [];

    const possibleips = [];

    likelyusers.push(spiHelper_caseName);

    if (results) {

    for (let i = 0; i < results.length; i++) {

    const username = spiHelper_normalizeUsername(results[i].replace(checkuser_re, '$1'));

    const isIP = mw.util.isIPAddress(username, true);

    if (!isIP && !likelyusers.includes(username)) {

    likelyusers.push(username);

    } else if (isIP && !likelyips.includes(username)) {

    likelyips.push(username);

    }

    }

    }

    const user_re = /{{\s*(?:user|vandal|IP)[^\|}{]*?\s*\|\s*(?:1=)?\s*([^\|}]*?)\s*}}/gi;

    const userresults = pagetext.match(user_re);

    if (userresults) {

    for (let i = 0; i < userresults.length; i++) {

    const username = spiHelper_normalizeUsername(userresults[i].replace(user_re, '$1'));

    if (mw.util.isIPAddress(username, true) && !possibleips.includes(username) &&

    !likelyips.includes(username)) {

    possibleips.push(username);

    } else if (!possibleusers.includes(username) &&

    !likelyusers.includes(username)) {

    possibleusers.push(username);

    }

    }

    }

    // Wire up the "select all" options

    $('#spiHelper_block_doblock', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_acb', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_ab', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_tp', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_email', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_lock', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_lock', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    spiHelper_generateSelect('spiHelper_block_tag', spiHelper_TAG_OPTIONS);

    $('#spiHelper_block_tag', $actionView).on('change', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    spiHelper_generateSelect('spiHelper_block_tag_altmaster', spiHelper_ALTMASTER_TAG_OPTIONS);

    $('#spiHelper_block_tag_altmaster', $actionView).on('change', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    $('#spiHelper_block_lock', $actionView).on('click', function (e) {

    spiHelper_setAllBlockOpts($(e.target));

    });

    for (let i = 0; i < likelyusers.length; i++) {

    spiHelper_usercount++;

    spiHelper_generateBlockTableLine(likelyusers[i], true, spiHelper_usercount);

    }

    for (let i = 0; i < likelyips.length; i++) {

    spiHelper_usercount++;

    spiHelper_generateBlockTableLine(likelyips[i], true, spiHelper_usercount);

    }

    for (let i = 0; i < possibleusers.length; i++) {

    spiHelper_usercount++;

    spiHelper_generateBlockTableLine(possibleusers[i], false, spiHelper_usercount);

    }

    for (let i = 0; i < possibleips.length; i++) {

    spiHelper_usercount++;

    spiHelper_generateBlockTableLine(possibleips[i], false, spiHelper_usercount);

    }

    } else {

    $('#spiHelper_blockTagView', $actionView).hide();

    }

    if (!spiHelper_ActionsSelected.Close) {

    $('#spiHelper_closeView', $actionView).hide();

    }

    if (spiHelper_ActionsSelected.Rename) {

    if (spiHelper_sectionId) {

    $('#spiHelper_moveHeader', $actionView).text('Move section "' + spiHelper_sectionName + '"');

    } else {

    $('#spiHelper_moveHeader', $actionView).text('Move/merge full case');

    }

    } else {

    $('#spiHelper_moveView', $actionView).hide();

    }

    if (!spiHelper_ActionsSelected.Archive) {

    $('#spiHelper_archiveView', $actionView).hide();

    }

    // Only give the option to comment if we selected a specific section

    if (spiHelper_sectionId) {

    // generate the note prefixes

    /** @type {SelectOption[]} */

    const spiHelper_noteTemplates = [

    { label: 'Comment templates', selected: true, value: '', disabled: true }

    ];

    if (spiHelper_isClerk()) {

    spiHelper_noteTemplates.push({ label: 'Clerk note', selected: false, value: 'clerknote' });

    }

    if (spiHelper_isAdmin()) {

    spiHelper_noteTemplates.push({ label: 'Administrator note', selected: false, value: 'adminnote' });

    }

    if (spiHelper_isCheckuser()) {

    spiHelper_noteTemplates.push({ label: 'CU note', selected: false, value: 'cunote' });

    }

    spiHelper_noteTemplates.push({ label: 'Note', selected: false, value: 'takenote' });

    // Wire up the select boxes

    spiHelper_generateSelect('spiHelper_noteSelect', spiHelper_noteTemplates);

    $('#spiHelper_noteSelect', $actionView).on('change', function (e) {

    spiHelper_insertNote($(e.target));

    });

    spiHelper_generateSelect('spiHelper_adminSelect', spiHelper_ADMIN_TEMPLATES);

    $('#spiHelper_adminSelect', $actionView).on('change', function (e) {

    spiHelper_insertTextFromSelect($(e.target));

    });

    spiHelper_generateSelect('spiHelper_cuSelect', spiHelper_CU_TEMPLATES);

    $('#spiHelper_cuSelect', $actionView).on('change', function (e) {

    spiHelper_insertTextFromSelect($(e.target));

    });

    $('#spiHelper_previewLink', $actionView).on('click', function () {

    spiHelper_previewText();

    });

    } else {

    $('#spiHelper_commentView', $actionView).hide();

    }

    // Wire up the submit button

    $('#spiHelper_performActions', $actionView).on('click', () => {

    spiHelper_performActions();

    });

    // Hide items based on role

    if (!spiHelper_isCheckuser()) {

    // Hide CU options from non-CUs

    $('.spiHelper_cuClass', $actionView).hide();

    }

    if (!spiHelper_isAdmin()) {

    // Hide block options from non-admins

    $('.spiHelper_adminClass', $actionView).hide();

    }

    if (!(spiHelper_isAdmin() || spiHelper_isClerk())) {

    $('.spiHelper_adminClerkClass', $actionView).hide();

    }

    }

    /**

    * Archives everything on the page that's eligible for archiving

    */

    async function spiHelper_oneClickArchive() {

    'use strict';

    const pagetext = await spiHelper_getPageText(spiHelper_pageName, false);

    spiHelper_caseSections = await spiHelper_getInvestigationSectionIDs();

    if (!spiHelper_SECTION_RE.test(pagetext)) {

    alert('Looks like the page has been archived already.');

    return;

    }

    displayMessage('