Usuario:Althair/massBlock.js
Nota: Después de guardar, debes refrescar la caché de tu navegador para ver los cambios. Internet Explorer: mantén presionada Ctrl mientras pulsas Actualizar. Firefox: mientras presionas Mayús pulsas el botón Actualizar, (o presiona Ctrl-Shift-R). Los usuarios de Google Chrome y Safari pueden simplemente pulsar el botón Recargar. Para más detalles e instrucciones acerca de otros exploradores, véase Ayuda:Cómo limpiar la caché.
/* jshint maxerr: 10000 */
/* global mediaWiki, jQuery, OO, window, document */
/**
* Tool for mass-blocking a list of IPs/users.
* Adapted from [[:en:User:Timotheus Canens/massblock.js]].
* Go to [[Special:Massblock]] to use it.
*
* In order to use it, please IMPORT this page, and then add some code calling
* mw.messages.set with a list of localized messages if you wish. For an example,
* see [[:it:MediaWiki:Gadget-Massblock-it.js]]
*
* Memo: only then() can return a new promise object, done() and fail() can't.
*
* @author (i.e. blame him) [[:it:User:Daimona Eaytoy]]
*/
( function( mw, $ ) {
'use strict';
// Default messages
mw.messages.set( {
'massblock-toolbar-text': 'Massblock',
'massblock-document-title': 'Mass-blocking tool - Wikipedia, the free encyclopedia',
'massblock-page-title': 'Mass-blocking tool',
'massblock-abuse-disclaimer': 'If you abuse this tool, it\'s your fault, not mine.',
'massblock-blockusers': 'Users to block (one on each line, please):',
'massblock-talkmsg': 'Replace talk page with, or text to add if the expiry is not infinite (leave blank to leave no message):',
'massblock-upmsg': 'Replace user page text with (leave blank for no change):',
'massblock-block-options-label': 'Block options',
'massblock-further-options-label': 'Further options',
'massblock-common-reasons': 'Common reasons:',
'massblock-other-reason': 'Other reason',
'massblock-extra-reason': 'Other/additional reason:',
'massblock-exptime': 'Expiration time, in english (blank for indefinite):',
'massblock-summary-default': 'Blocked user.',
'massblock-talksummary': 'Edit summary for talk page edit:',
'massblock-talkprotect': 'Protect the talk (sysop level, infinite):',
'massblock-upsummary': 'Edit summary for user page edit:',
'massblock-upprotect': 'Protect the user page (sysop level, infinite):',
'massblock-protect-reason-label': 'Reason for protection:',
'massblock-protect-reason-default': 'Blocked user.',
'massblock-anononly': 'Block anonymous users only (IPs only):',
'massblock-autoblock': 'Enable autoblock (accounts only):',
'massblock-nocreate': 'Block account creation:',
'massblock-noemail': 'Block email:',
'massblock-notalk': 'Remove talk page access:',
'massblock-override': 'Override existing blocks:',
'massblock-submit-text': 'Block',
'massblock-result-alert': 'Blocked $1 users. Edited $2 talk pages and $3 user pages. Protected $4 talk pages and $5 user pages.',
'massblock-failed-actions': 'Failed actions with errors:',
'massblock-failure-help': 'help',
'massblock-init-failure': 'Unable to load the Massblock gadget. Error:'
} );
// OOUI element
var submitBtn,
// Ideally this would use $wgBlockAllowsUTEdit, but it's not available.
blockAllowsTalkEdit = window.location.href.indexOf( 'it.wikipedia' ) === -1,
mwapi = new mw.Api();
var ErrorHandler = {
errors: {},
/**
* Process an error in any part of the process
*
* @param {string} e The error code
* @param {string} user The user we're processing
* @param {string} action The action we're doing. This is of the form
* {$actionname}-{$page}, where $action name is the name of the API module
* being used (e.g. 'block' or 'edit') and $page is either 'talk' or 'user',
* representing the target of the action. The only special case is 'block',
* which has no hyphen and no page.
*/
add: function( e, user, action ) {
var obj = { action: e };
if ( !this.errors[ user ] ) {
this.errors[ user ] = [ obj ];
} else {
this.errors[ user ].push( obj );
}
},
getCodesForUser: function( user ) {
var cb = function( el ) {
var key = Object.keys( el )[ 0 ],
action = key.split( "-" )[ 0 ],
link = '//mediawiki.org/wiki/API:' + action + '#Possible_errors';
return key + ': <code style="color:red">' +
el[ key ] + '</code> (<a href="' + link + '">' +
msg( 'failure-help' ) + '</a>)';
};
return this.errors[ user ].map( cb );
}
};
var FormData = {
init: function() {
this.dropdownReason = $( "#wpMassBlockReasons select :selected" ).val().trim();
this.otherReason = $( "#wpMassBlockReason input" ).val().trim();
this.anononly = $( "#wpMassBlockAnononly input" ).prop( 'checked' );
this.nocreate = $( "#wpMassBlockNocreate input" ).prop( 'checked' );
this.noEmail = $( "#wpMassBlockEmail input" ).prop( 'checked' );
this.autoblock = $( "#wpMassBlockAutoblock input" ).prop( 'checked' );
this.talkpageBlocked = blockAllowsTalkEdit ? $( "#wpMassBlockTalkpage input" ).prop( 'checked' ) : true;
this.reblock = $( "#wpMassBlockReblock input" ).prop( 'checked' );
this.talkMessage = $( "#wpMassBlockMessage textarea" ).val().trim();
this.expiry = $( "#wpMassBlockExpiry input" ).val().trim();
this.talkSummary = $( "#wpMassBlockSummaryTalk input" ).val().trim();
this.upSummary = $( "#wpMassBlockSummaryUser input" ).val().trim();
this.protectReason = $( "#wpMassBlockProtectReason input" ).val().trim();
this.isInfty = isInfinity( this.expiry );
// Several actions can only be executed if the block is infinite
this.protectTalk = $( "#wpMassBlockProtectTalk input" ).prop( 'checked' ) && this.isInfty;
this.protectUser = $( "#wpMassBlockProtectUser input" ).prop( 'checked' ) && this.isInfty;
this.upMessage = this.isInfty ? $( "#wpMassBlockTag textarea" ).val().trim() : '';
}
};
var Main = {
blocked: 0,
talkpageedited: 0,
userpageedited: 0,
talkpageprotected: 0,
userpageprotected: 0,
_protect: function( page, exists, counter, user ) {
var prType = exists ? 'edit=sysop|move=sysop' : 'create=sysop';
return doProtectPage( page, 'edit=sysop|move=sysop' )
.done( function() {
this[counter]++;
} )
.fail( function( e ) {
ErrorHandler.add( e, user, "protect-talk" );
} )
.always( function() {
return $.when();
} );
},
editTalk: function( user ) {
var talkPage = getTalkTitle( user );
return doEditPage( talkPage, FormData.talkMessage, FormData.talkSummary, !FormData.isInfty )
.then(
function() {
this.talkpageedited++;
if ( FormData.protectTalk ) {
return this._protect( talkPage, true, 'talkpageprotected', user );
}
}.bind( this ),
function( e ) {
ErrorHandler.add( e, user, "edit-talk" );
return $.when();
}
);
},
protectTalk: function( user ) {
var talkPage = getTalkTitle( user );
mwapi.get( {
action: 'query',
titles: talkPage
} )
.then(
function( data ) {
var exists = Object.keys( data.query.pages )[ 0 ] !== -1;
return this._protect( talkPage, exists, 'talkpageprotected', user );
}.bind( this ),
function( e ) {
ErrorHandler.add( e, user, "query-talk" );
return $.when();
}
);
},
editUP: function( user ) {
var userPage = getUPTitle( user );
doEditPage( userPage, FormData.upMessage, FormData.upSummary )
.then(
function() {
this.userpageedited++;
if ( FormData.protectUser ) {
return this._protect( userPage, true, 'userpageprotected', user );
}
}.bind( this ),
function( e ) {
ErrorHandler.add( e, user, "edit-user" );
return $.when();
}
);
},
protectUP: function( user ) {
var userPage = getUPTitle( user );
return mwapi.get( {
action: 'query',
titles: userPage
} )
.then(
function( data ) {
var exists = Object.keys( data.query.pages )[ 0 ] !== -1;
return this._protect( userPage, exists, 'userpageprotected', user );
}.bind( this ),
function( e ) {
ErrorHandler.add( e, user, "query-user" );
return $.when();
}
);
},
getAlertText: function() {
return msg( 'result-alert' )
.replace( '$1', this.blocked )
.replace( '$2', this.talkpageedited )
.replace( '$3', this.userpageedited )
.replace( '$4', this.talkpageprotected )
.replace( '$5', this.userpageprotected );
}
};
function getTalkTitle( user ) {
return 'User talk:' + user;
}
function getUPTitle( user ) {
return 'User:' + user;
}
/**
* The post-block handler. Performs edits and protections.
*
* @param {string} user
* @return {Promise} A promise which is resolved after all actions are done
* for all pages. This will never be rejected.
*/
function successHandler( user ) {
// @var {Promise} These track the state for all actions (edit and protect)
// on every page. They are resolved as soon as a page is processed, with or
// without a failure. This way the final $.when will resolve after all promises
// have been processed, and not after the first rejection.
var talkDone, userDone;
Main.blocked++;
if ( FormData.talkMessage !== "" ) {
talkDone = Main.editTalk( user );
} else if ( FormData.protectTalk ) {
talkDone = Main.protectTalk( user );
} else {
talkDone = $.when();
}
if ( FormData.upMessage !== "" ) {
userDone = Main.editUP( user );
} else if ( FormData.protectUser ) {
userDone = Main.protectUP( user );
} else {
userDone = $.when();
}
return $.when( talkDone, userDone );
}
/**
* Main form processing routine, called on form submit
*/
function doMassBlock() {
var users = $( "#wpMassBlockUsers textarea" ).val().split( "\n" );
users = users
// First trim everything
.map( function( s ) {
return s.trim();
} )
// Then remove blanks and duplicates
.filter( function( el, index, me ) {
return el !== '' && me.indexOf( el ) === index;
} );
if ( users.length === 0 ) {
// Easy
return;
}
submitBtn.setDisabled( true );
FormData.init();
// Array of Promises, one for each user. Each one is resolved after the user
// is processed, even in case of failure.
var deferreds = [];
users.forEach( function( user ) {
deferreds.push(
doBlock( user )
.then(
function() {
return successHandler( user );
},
function( e ) {
ErrorHandler.add( e, user, "block" );
// Return so that this counts as resolved and won't leave
// other promises unresolved.
return $.when();
}
)
);
} );
$.when.apply( $, deferreds ).always( doPostBlockActions );
}
/**
* Executed after all users have been processed.
*/
function doPostBlockActions() {
var errors = ErrorHandler.errors;
if ( Object.keys( errors ).length > 0 ) {
var linkedList = '';
for ( var user in errors ) {
var codes = ErrorHandler.getCodesForUser( user );
linkedList +=
"<li><a href=\"" + mw.config.get( 'wgScript' ) + "?title=Special:Contributions/" + encodeURIComponent( user ) + "\">" +
user + "</a>: " + codes.join( "; " ) + "</li>";
}
$( "#wpMassBlockFailedContainer" ).html(
'<h3>' + msg( 'failed-actions' ) + '</h3><ul>' + linkedList + '</ul><hr />'
);
}
OO.ui.alert( Main.getAlertText() );
}
/**
* Perform a single block via API
*
* @param {string} user The user to block
* @return {Promise}
*/
function doBlock( user ) {
return mwapi.postWithToken( "csrf", {
action: 'block',
allowusertalk: !FormData.talkpageBlocked,
autoblock: FormData.autoblock,
nocreate: FormData.nocreate,
expiry: FormData.expiry === "" ? "indefinite" : FormData.expiry,
anononly: FormData.anononly,
noemail: FormData.noEmail,
reblock: FormData.reblock,
reason: FormData.dropdownReason === "other" ? FormData.otherReason : FormData.dropdownReason + ( FormData.otherReason ? ": " + FormData.otherReason : "" ),
user: user
} );
}
/**
* Edit the given page.
*
* @param {string} title The title of the page
* @param {string} text The text to add
* @param {string} summary The summary to use
* @param {bool} append Whether to append the text or replace the whole page content
* @return {Promise}
*/
function doEditPage( title, text, summary, append ) {
var appendText = append || false,
params = {
action: 'edit',
title: title,
summary: summary,
watchlist: 'nochange'
};
if ( appendText ) {
params.appendtext = text;
} else {
params.text = text;
}
return mwapi.postWithEditToken( params );
}
/**
* Protect the given page
*
* @param {string} title The page to protect
* @param {string} protections As accepted by the Protect API module
* @return {Promise}
*/
function doProtectPage( title, protections ) {
return mwapi.postWithToken( 'csrf', {
action: 'protect',
title: title,
protections: protections,
reason: FormData.protectReason,
watchlist: 'nochange'
} );
}
/**
* Get a localised messages, or the default one as fallback.
*
* @param {string} msg The key of the message to get
* @return {string}
*/
function msg( msg ) {
return mw.messages.get( 'massblock-' + msg );
}
/**
* Build the form
*/
function massblockform() {
var reasons = mw.msg( 'Ipbreason-dropdown' ).split( '**' ),
// OOUI elements
talkTextField, userTextField, talkProtectCb, talkSummaryField,
userSummaryField, userProtectCb, protectReasonField;
$( "h1" ).first().html( msg( 'page-title' ) );
document.title = msg( 'document-title' );
var form = new OO.ui.FormLayout( {
id: 'wpMassBlock'
} );
talkTextField = new OO.ui.MultilineTextInputWidget( {
rows: 10
} );
userTextField = new OO.ui.MultilineTextInputWidget( {
rows: 10
} );
var mainField = new OO.ui.FieldsetLayout( {
label: msg( 'blockusers' ),
items: [
new OO.ui.MultilineTextInputWidget( {
rows: 10,
id: 'wpMassBlockUsers'
} )
]
} );
var reasonOpts = [ {
optgroup: msg( 'other-reason' )
},
{
data: 'other',
label: msg( 'other-reason' )
},
{
optgroup: msg( 'common-reasons' )
}
];
for ( var i = 1, j = reasons.length; i < j; i++ ) {
reasonOpts.push( {
data: reasons[ i ],
label: reasons[ i ]
} );
}
var expiryField = new OO.ui.TextInputWidget( {
maxLength: 255,
id: 'wpMassBlockExpiry'
} );
var otherReasonField = new OO.ui.TextInputWidget( {
maxLength: 255,
id: 'wpMassBlockReason'
} );
var reasonsDropdown = new OO.ui.DropdownInputWidget( {
options: reasonOpts,
id: 'wpMassBlockReasons'
} ).on( 'change', function() {
var reason = reasonsDropdown.getValue(),
maxlength = ( reason === "other" ? 255 : ( 255 - ': '.length ) - reason.length );
$( '#wpMassBlockReason input' ).attr( "maxlength", maxlength );
} );
var blockOptsArray = [
new OO.ui.FieldLayout(
reasonsDropdown, {
label: msg( 'common-reasons' )
}
),
new OO.ui.FieldLayout(
otherReasonField, {
label: msg( 'extra-reason' )
}
),
new OO.ui.FieldLayout( expiryField, {
label: msg( 'exptime' )
} ),
new OO.ui.FieldLayout(
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockAnononly',
selected: true
} ), {
label: msg( 'anononly' )
}
),
new OO.ui.FieldLayout(
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockAutoblock',
selected: true
} ), {
label: msg( 'autoblock' )
}
),
new OO.ui.FieldLayout(
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockNocreate',
selected: true
} ), {
label: msg( 'nocreate' )
}
),
new OO.ui.FieldLayout(
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockEmail'
} ), {
label: msg( 'noemail' )
}
)
];
if ( blockAllowsTalkEdit ) {
blockOptsArray.push( new OO.ui.FieldLayout(
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockTalkpage'
} ), {
label: msg( 'notalk' )
}
) );
}
blockOptsArray.push( new OO.ui.FieldLayout(
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockReblock'
} ), {
label: msg( 'override' )
}
) );
var blockOpts = new OO.ui.FieldsetLayout( {
label: msg( 'block-options-label' ),
items: blockOptsArray
} );
talkSummaryField =
new OO.ui.TextInputWidget( {
maxLength: 255,
id: 'wpMassBlockSummaryTalk',
value: msg( 'summary-default' ),
// The text is empty by default
disabled: true
} );
talkTextField.on( 'change', function() {
talkSummaryField.setDisabled( talkTextField.getValue().trim() === '' );
} );
userSummaryField =
new OO.ui.TextInputWidget( {
maxLength: 255,
id: 'wpMassBlockSummaryUser',
value: msg( 'summary-default' ),
// The text is empty by default
disabled: true
} );
var toggleUserTextField = function() {
var disabled = userTextField.getValue().trim() === '' ||
!isInfinity( $( '#wpMassBlockExpiry input' ).val().trim() );
userSummaryField.setDisabled( disabled );
};
userTextField.on( 'change', toggleUserTextField );
talkProtectCb =
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockProtectTalk'
} );
userProtectCb =
new OO.ui.CheckboxInputWidget( {
id: 'wpMassBlockProtectUser'
} );
protectReasonField =
new OO.ui.TextInputWidget( {
maxLength: 255,
id: 'wpMassBlockProtectReason',
value: msg( 'protect-reason-default' ),
disabled: true
} );
/**
* Toggle protection fields
*/
var toggleProtectionFields = function() {
if (
!( talkProtectCb.isSelected() || userProtectCb.isSelected() ) ||
!isInfinity( $( '#wpMassBlockExpiry input' ).val().trim() )
) {
protectReasonField.setDisabled( true );
} else {
protectReasonField.setDisabled( false );
}
};
talkProtectCb.on( 'change', toggleProtectionFields );
userProtectCb.on( 'change', toggleProtectionFields );
var furtherOpts = new OO.ui.FieldsetLayout( {
label: msg( 'further-options-label' ),
items: [
new OO.ui.HorizontalLayout( {
items: [
new OO.ui.FieldLayout(
talkTextField,
{
label: msg( 'talkmsg' ),
align: 'top',
id: 'wpMassBlockMessage',
classes: [ 'massblock-horiz-element' ]
}
),
new OO.ui.FieldLayout(
userTextField,
{
label: msg( 'upmsg' ),
align: 'top',
id: 'wpMassBlockTag',
classes: [ 'massblock-horiz-element' ]
}
)
]
} ),
new OO.ui.FieldLayout( talkSummaryField, {
label: msg( 'talksummary' )
} ),
new OO.ui.FieldLayout( userSummaryField, {
label: msg( 'upsummary' )
} ),
new OO.ui.FieldLayout( talkProtectCb, {
label: msg( 'talkprotect' )
} ),
new OO.ui.FieldLayout( userProtectCb, {
label: msg( 'upprotect' )
} ),
new OO.ui.FieldLayout( protectReasonField, {
label: msg( 'protect-reason-label' )
} )
]
} );
expiryField.on( 'change', function() {
// Several fields cannot be used if the expiry isn't infinite
var enable = isInfinity( $( '#wpMassBlockExpiry input' ).val().trim() ),
// These are OOUI elements
disableEls = [
userTextField,
talkProtectCb,
userProtectCb
];
for ( var el in disableEls ) {
disableEls[ el ].setDisabled( !enable );
}
toggleProtectionFields();
toggleUserTextField();
} );
submitBtn = new OO.ui.ButtonInputWidget( {
label: msg( 'submit-text' ),
title: msg( 'submit-text' ),
id: 'wpMassBlockSubmit',
flags: [
'primary',
'progressive'
]
} )
.on( 'click', doMassBlock );
form.addItems( [ mainField, blockOpts, furtherOpts, new OO.ui.FieldLayout( submitBtn ) ] );
var bodyContentID = ( mw.config.get( 'skin' ) === "cologneblue" ? "#article" : "#bodyContent" );
$( bodyContentID ).html( '<div style="font-size:150%"><u>' + msg( 'abuse-disclaimer' ) + '</u></div><br /><hr /><div id="wpMassBlockFailedContainer"></div>' );
$( bodyContentID ).append( form.$element );
}
/**
* Utility function to tell if an expiry is infinite
*
* @param {string} expiry
* @return {bool}
*/
function isInfinity( expiry ) {
return /^(indefinite|infinite|infinity|never|)$/i.test( expiry );
}
$( function() {
if ( mw.config.get( "wgNamespaceNumber" ) === -1 &&
( mw.config.get( "wgTitle" ) === "Massblock" || mw.config.get( "wgTitle" ) === "MassBlock" ) &&
( /sysop/ ).test( mw.config.get( "wgUserGroups" ) )
) {
var style ='.massblock-horiz-element{ width: 40%; }';
mw.util.addCSS( style );
mw.loader.using( [ 'mediawiki.jqueryMsg', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows' ], $.ready )
.done( function loadMsg() {
mwapi.loadMessagesIfMissing( [ 'Ipbreason-dropdown' ] )
.done( massblockform )
.fail( function ( e ) {
mw.log.error( msg( 'init-failure' ) + ' ' + e );
} );
} )
.fail( function ( e ) {
mw.log.error( msg( 'init-failure' ) + ' ' + e );
} );
} else {
mw.util.addPortletLink( 'p-tb', mw.config.get( 'wgScript' ) + '?title=Special:Massblock', msg( 'toolbar-text' ) );
}
} );
}( mediaWiki, jQuery ) );