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:MassGlobalBlock * 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. */ /* jslint strict:false */ // mw.loader.using( ['mediawiki.api', 'oojs-ui'], function {	if( mw.config.get( 'wgPageName' ) !== 'Special:MassGlobalBlock' ) {		return;	}

var api = new mw.Api;

function GroupEnforcerProc( config ) { GroupEnforcerProc.super.call( this, config ); }	OO.inheritClass( GroupEnforcerProc, OO.ui.ProcessDialog );

GroupEnforcerProc.static.name = 'gEnforceProc'; GroupEnforcerProc.static.title = 'Group Enforcer'; GroupEnforcerProc.static.actions = [ {			action: 'save', label: 'Proceed', flags: ['primary','progressive'] },		{			label: 'Cancel', flags: 'safe' }	];

var gepEditFlag = new OO.ui.CheckboxInputWidget( {		id: 'gep-edit-flag'	} ); gepEditFlag.on( 'change', function ( selected ) {		var disable = !selected;		// gepGroup.setDisabled( disable );		gepReason.setDisabled( disable );		gepRevertReason.setDisabled( disable );	} );

var gepGroup = new OO.ui.TextInputWidget( {		id: 'gep-group',		value: 'flood',		disabled: true,		required: true,		validate: 'not-empty'	} );

var gepReason = new OO.ui.TextInputWidget( {		id: 'gep-reason',		value: 'Performing mass blocking',		disabled: true,		required: true,		validate: 'not-empty'	} );

var gepRevertReason = new OO.ui.TextInputWidget( {		id: 'gep-revert-reason',		value: 'Done',		disabled: true,		required: true,		validate: 'not-empty'	} );

var gepRevertButton = new OO.ui.ButtonWidget( {		id: 'gep-revert-button',		label: 'Remove group',		flags: [ 'destructive', 'primary' ]	} ); gepRevertButton.on( 'click', function {		modifyGroups( false );		windowManager.closeWindow( groupEnforcer );	} );

GroupEnforcerProc.prototype.initialize = function { GroupEnforcerProc.super.prototype.initialize.apply( this, arguments );

this.content = new OO.ui.FieldsetLayout( {			label: 'Enforcement options'		} ); this.content.addItems( [			new OO.ui.FieldLayout( gepEditFlag, { label: 'Enable editing of options', align: 'inline' } ),			new OO.ui.FieldLayout( gepGroup, { label: 'Group:', align: 'inline' } ),			new OO.ui.FieldLayout( gepReason, { label: 'Reason:', align: 'inline', help: 'Only used if adding the group!', helpInline: true } ),			new OO.ui.FieldLayout( gepRevertReason, { label: 'Removal reason:', align: 'inline', help: 'Only used if you use this form to remove yourself from the enforced group!', helpInline: true } ),			new OO.ui.FieldLayout( gepRevertButton ),		] );		this.content.$element.css( 'padding', '1em' );

this.$body.append( this.content.$element ); };

GroupEnforcerProc.prototype.getActionProcess = function ( action ) { var dialog = this; if ( action ) { return new OO.ui.Process( function {				if ( action == 'save' ) {					modifyGroups( true );				}

dialog.close( {					action: action				} ); } );		}		return GroupEnforcerProc.super.prototype.getActionProcess.call( this, action );	};

GroupEnforcerProc.prototype.getBodyHeight = function { return this.content.$element.outerHeight( true ); };

var windowManager = new OO.ui.WindowManager; var groupEnforcer = new GroupEnforcerProc( {		size: 'small'	} ); windowManager.addWindows( [ groupEnforcer ] );

var inEnforcerButton = new OO.ui.ButtonWidget( {		id: 'mgb-enforcer-button',		label: 'Group enforcer',		value: 1	} ); inEnforcerButton.on( 'click', function {		windowManager.openWindow( groupEnforcer );	} );

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

var inExpiry = new OO.ui.ComboBoxInputWidget( {		id: 'mgb-expiry',		disabled: true,		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',		disabled: true,		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',		disabled: true,		selected: true,		value: 1	} );

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

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

var inSubmit = new OO.ui.ButtonWidget( {		id: 'mgb-submit',		disabled: true,		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( inEnforcerButton ),		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 ); } ); }

function modifyGroups ( adding ) { api.postWithToken( 'userrights', {			format: 'json',			action: 'userrights',			user: mw.config.get( 'wgUserName' ),			add: adding ? gepGroup.value : ,			remove: adding ?  : gepGroup.value,			reason: adding ? gepReason.value : gepRevertReason.value		} ).done( permChecks ); }

function permChecks { api.getUserInfo.done(function ( data ) {			if ( data.groups.includes( 'flood' ) ) {				inRanges.setDisabled( false );				inExpiry.setDisabled( false );				inReason.setDisabled( false );				inAnonOnly.setDisabled( false );				inGlobalBlocks.setDisabled( false );				inLocalBlocks.setDisabled( false );				inSubmit.setDisabled( false );			} else {				inRanges.setDisabled( true );				inExpiry.setDisabled( true );				inReason.setDisabled( true );				inAnonOnly.setDisabled( true );				inGlobalBlocks.setDisabled( true );				inLocalBlocks.setDisabled( true );				inSubmit.setDisabled( true );			}		} ); }

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