User:Zppix/testgblock.js

Revision as of 21:38, 7 September 2019 by Zppix (talk | contribs)

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.
/**
 * 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.
 */

mw.loader.using( ['mediawiki.api', 'oojs-ui'], function () {
	if( mw.config.get( 'wgPageName' ) !== 'Special:MassGlobalBlock' ) {
		if( mw.config.get('wgUserGroups').includes( 'testgroup' ) ) {
		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{{@}}miraheze.org if affected.' },
			{ data: 'Crosswiki abuse. Contact cvt{{@}}miraheze.org if affected.' },
			{ data: 'Vandalism. Contact cvt{{@}}miraheze.org if affected.' },
			{ data: 'Long term abuse. Contact cvt{{@}}miraheze.org if affected.' },
			{ data: 'Web host or proxy. Contact cvt{{@}}miraheze.org if affected.' }
		],
		required: true,
		value: 'Web host or proxy. Contact cvt{{@}}miraheze.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 );

} );