MediaWiki:Gadget-MassGlobalBlock.js

/** * Script that allows for mass (global) blocking via Special:MassGlobalBlock * To use, add the following to your common.js: * mw.loader.load( https://meta.miraheze.org/w/index.php?title=User:Void/massGBlock.js&action=raw&ctype=text/javascript ); * Then, navigate to Special:MassBlock * Note: this script is only compatible with IPv4 ranges. * All ranges listed must be on their own line and be in a valid CIDR format. */

mw.loader.using( ['mediawiki.api', 'oojs-ui'], function {	if( mw.config.get( 'wgPageName' ) !== 'Special:MassGlobalBlock' ) {		return;	}

var api = new mw.Api;

var inRanges = new OO.ui.MultilineTextInputWidget( {		id: 'mgb-ranges',		required: true,		rows: 10,		validate: 'not-empty'	} );

var inExpiry = new OO.ui.ComboBoxInputWidget( {		id: 'mgb-expiry',		options: [			{ data: '1 month' },			{ data: '3 months' },			{ data: '6 months' },			{ data: '1 year' }		],		required: true,		value: '1 year',		validate: 'not-empty'	} );

var inReason = new OO.ui.ComboBoxInputWidget( {		id: 'mgb-reason',		options: [			{ data: 'Crosswiki spamming. Contact cvt@undefinedmiraheze.org if affected.' },			{ data: 'Crosswiki abuse. Contact cvt@undefinedmiraheze.org if affected.' },			{ data: 'Vandalism. Contact cvt@undefinedmiraheze.org if affected.' },			{ data: 'Long term abuse. Contact cvt@undefinedmiraheze.org if affected.' },			{ data: 'Web host or proxy. Contact cvt@undefinedmiraheze.org if affected.' }		],		required: true,		value: 'Web host or proxy. Contact cvt@undefinedmiraheze.org if affected.',		validate: 'not-empty'	} );

var inAnonOnly = new OO.ui.CheckboxInputWidget( {		id: 'mgb-anononly',		selected: true,		value: 1	} );

var inGlobalBlocks = new OO.ui.CheckboxInputWidget( {		id: 'mgb-globalblocks',		selected: true,		value: 1	} );

var inLocalBlocks = new OO.ui.CheckboxInputWidget( {		id: 'mgb-localblocks',		selected: true,		value: 1	} );

var inSubmit = new OO.ui.ButtonWidget( {		id: 'mgb-submit',		label: 'Submit',		flags: ['destructive', 'primary']	} ); inSubmit.on( 'click', doSubmit );

var mgbForm = new OO.ui.FieldsetLayout( {		label: 'Mass blocking interface'	} ); mgbForm.addItems( [		new OO.ui.FieldLayout( inRanges, { label: 'IP Ranges:', align: 'top', help: 'Only works with IPv4!', helpInline: true } ),		new OO.ui.FieldLayout( inExpiry, { label: 'Expiry:', align: 'top' } ),		new OO.ui.FieldLayout( inReason, { label: 'Reason', align: 'top' } ),		new OO.ui.FieldLayout( inAnonOnly, { label: 'Block anonymous users only', align: 'inline' } ),		new OO.ui.FieldLayout( inGlobalBlocks, { label: 'Block ranges globally', align: 'inline' } ),		new OO.ui.FieldLayout( inLocalBlocks, { label: 'Block ranges locally', align: 'inline' } ),		new OO.ui.FieldLayout( inSubmit ),	] );

var IPRange = function IPR ( value ) { this.getRange = function { return this.ip + '/' + this.cidr; };

this.getDecimal = function { var dots = this.ip.split( '.' ); if( dots.length !== 4 ) { throw 'Invalid IP ' + this.ip; }			return ( ( ( ( ( parseInt( dots[0] ) * 256 ) + parseInt( dots[1] ) ) * 256 ) + parseInt( dots[2] ) ) * 256 ) + parseInt( dots[3] ); };

this.getNext = function { var jump = Math.pow( 2, 32 - parseInt( this.cidr ) ); return this.getDecimal + jump; };

this.combine = function ( other ) { var ourDecimal = this.getDecimal, otherDecimal = other.getDecimal; if( ourDecimal > otherDecimal ) { return other.combine( this ); // Impossible, but let's account for it anyway }

var ip = null, cidr = null; if( ourDecimal === otherDecimal ) { ip = this.ip; cidr = ( parseInt( this.cidr ) < parseInt( other.cidr ) ? this.cidr : other.cidr ); } else if( ourDecimal < otherDecimal && this.getNext >= other.getNext ) { ip = this.ip; cidr = this.cidr; } else if( this.getNext == otherDecimal && this.cidr === other.cidr ) { ip = this.ip; cidr = '' + ( parseInt( this.cidr ) - 1 ); } else { throw 'Cannot combine ' + this.getRange + ' with ' + other.getRange; }			return new IPRange( ip + '/' + cidr ); };

var vlist = value.split( '/' ); if( vlist.length !== 2 ) { throw 'Invalid CIDR'; }		this.ip = vlist[0]; this.cidr = vlist[1]; if( this.getDecimal % Math.pow( 2, ( 32 - parseInt( this.cidr ) ) ) ) { throw 'Invalid IP range'; }	};

function consolidateIPRanges ( ranges ) { var cleanList = []; for( i = 0; i < ranges.length; i++ ) { try { cleanList.push( new IPRange( ranges[i] ) ); } catch( e ) { console.log( 'Invalid input: ' + ranges[i], e ); }		}		ranges = cleanList.sort( function ( a, b ) { return a.getDecimal - b.getDecimal; } );

var change = true; while( change ) { change = false; var cranges = []; for( i = 0; i < ranges.length; i++ ) { try { cranges[cranges.length - 1] = cranges[cranges.length - 1].combine( ranges[i] ); change = true; } catch( e ) { cranges.push( ranges[i] ); }

var change2 = true; while( change2 ) { change2 = false; var range = cranges.pop; try { cranges[cranges.length - 1].combine( range ); change2 = true; } catch( e ) { cranges.push( range ); }				}			}			ranges = cranges; }

return ranges; }

function doSubmit { if( !inGlobalBlocks.isSelected && !inLocalBlocks.isSelected ) { return alert( 'You must apply a block locally or globally!' ); }		if( !mgbForm.items.every( function ( e ) { return e.fieldWidget.value !== ''; } ) ) { return alert( 'Please fill all required parts of the form!' ); }

mgbForm.items.forEach( function ( e ) {			e.fieldWidget.setDisabled( true );		} );

var ranges = inRanges.value.split( '\n' ), deferred = null, conf = { reason: inReason.value, expiry: inExpiry.value, anononly: inAnonOnly.isSelected ? 1 : 0			};

ranges = consolidateIPRanges( ranges );

if( inGlobalBlocks.isSelected ) { deferred = makeBlockFunc( true, ranges[0].getRange, conf ); }		if( inLocalBlocks.isSelected ) { if( deferred ) { deferred = deferred.then( makeBlockFunc( false, ranges[0].getRange, conf ) ); } else { deferred = makeBlockFunc( false, ranges[0].getRange, conf ); }		}

for( i = 1; i < ranges.length; i++ ) { if( inGlobalBlocks.isSelected ) { deferred = deferred.then( makeBlockFunc( true, ranges[i].getRange, conf ) ); }			if( inLocalBlocks.isSelected ) { deferred = deferred.then( makeBlockFunc( false, ranges[i].getRange, conf ) ); }		}

$.when( deferred ).then( doFinish ); }

function makeBlockFunc ( global, range, config ) { return function { return $.Deferred( function ( deferred ) {				var data = {					format: 'json',					expiry: config.expiry,					reason: config.reason,					anononly: config.anononly				};

if ( global ) { data.action = 'globalblock'; data.target = range; } else { data.action = 'block'; data.user = range; data.nocreate = 1; }

var promise = api.postWithToken( 'csrf', data ); promise.fail( function ( e ) {					console.log( 'Could not ' + ( global ? 'globally' : 'locally' ) + ' block ' + range );					console.log( e );				} ); promise.always( function {					deferred.resolve;				} ); } );		};	}

function doFinish { alert( 'Done blocking' ); mgbForm.items.forEach( function ( e ) { e.fieldWidget.setDisabled( false ); } ); }

// Create UI	$( '.firstHeading' ).text( 'Mass Global Block' ); $( '#bodyContent' ).text( '' ); $( '#bodyContent' ).append( mgbForm.$element );

} );