Jump to content
Wikimedia Meta-Wiki

MediaWiki:Gadget-globalmassblock.js

From Meta, a Wikimedia project coordination wiki
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
 /**
  * Adds a client-side Special:MassGlobalBlock (404!) until a server-side solution is implemented.
  * See https://phabricator.wikimedia.org/T124607
  * Usage: Enable the gadget at [[Special:Gadgets]] and go to Special:MassGlobalBlock.
  */

 varSpecialMassGlobalBlock={
 name:'Mass global block',
 api:null,
 $content:$('#mw-content-text'),
 expiryTimes:['1 day','3 days','1 week','2 weeks','1 month','6 months','1 year','2 years','3 years'],
 blockReasons:['Cross-wiki spam: spambot','[[m:Special:MyLanguage/NOP|Open proxy]]: See the [[m:WM:OP/H|help page]] if you are affected','[[m:Special:MyLanguage/NOP|Open proxy/Webhost]]: See the [[m:WM:OP/H|help page]] if you are affected <!-- INSERT PROVIDER -->'],
 MAX_LIMIT:20000,

 execute:function(){
 SpecialMassGlobalBlock.api=newmw.Api();
 document.title=this.name+' - '+mw.config.get('wgSiteName');
 $('#firstHeading').text(this.name);
 this.$content.empty();
 this.$content.append(
 '<p> This page allows doing mass global blocks on lots of IP addresses or ranges at once. You can block '
 +'a maximum of '+this.MAX_LIMIT+' targets in one submission. Please use this tool with care.<br />'
 +'<em>Try not to flood StewardBot out of the IRC channel!</em> </p>'
 );
 this.$content.append(this.getFormPanel().$element);
 this.submit.on('click',this.onSubmit);
 },

 initFormWidgets:function(){
 // Validation callback for text inputs
 varisEmpty=function(val){
 if(val.trim()===''){
 returnfalse;
 }
 returntrue;
 };

 this.targets=newOO.ui.MultilineTextInputWidget({
 id:'mw-mgblock-targets',
 multiline:true,
 required:true,
 rows:20,
 maxRows:20000,
 autocomplete:false,
 placeholder:'List of IP addresses or ranges separated by newline',
 validate:isEmpty
 });

 this.expiry=newOO.ui.ComboBoxInputWidget({
 id:'mw-mgblock-expiry',
 required:true,
 options:this.expiryTimes.map(function(expiry){
 return{data:expiry};
 }),
 validate:isEmpty
 });

 this.reason=newOO.ui.ComboBoxInputWidget({
 id:'mw-mgblock-reason',
 required:true,
 options:this.blockReasons.map(function(reason){
 return{data:reason};
 }),
 validate:isEmpty
 });

 this.checkboxes=newOO.ui.CheckboxMultiselectInputWidget({
 id:'mw-mgblock-checkboxes',
 options:[{
 data:'anononly',
 label:'Block anonymous users only'
 },{
 data:'alsolocal',
 label:'Also block the given IP address locally on this wiki'
 },{
 data:'localblockstalk',
 label:'Block user from editing their own talk page locally'
 },{
 data:'modify',
 label:'Modify any existing blocks'
 }]
 });

 this.submit=newOO.ui.ButtonInputWidget({
 id:'mw-mgblock-submit',
 label:'Submit',
 flags:['primary','destructive']
 });
 },

 getFormFields:function(){
 return[{
 widget:this.targets,
 config:{
 label:'List of IP addresses and ranges to block',
 align:'top'
 }
 },{
 widget:this.expiry,
 config:{
 label:'Expiry',
 align:'top',
 }
 },{
 widget:this.reason,
 config:{
 label:'Reason',
 align:'top',
 }
 },{
 widget:this.checkboxes,
 config:{
 align:'inline'
 }
 }];
 },

 getFormPanel:function(){
 this.initFormWidgets();
 varformFields=this.getFormFields()
 .map(function(field){
 returnnewOO.ui.FieldLayout(field.widget,field.config);
 });
 varfieldset=newOO.ui.FieldsetLayout({
 items:formFields.concat(newOO.ui.FieldLayout(this.submit)),
 });
 returnnewOO.ui.PanelLayout({
 id:'mw-mgblock-form',
 expanded:false,
 $content:fieldset.$element
 });
 },

 disableForm:function(state){
 this.submit.setDisabled(state);
 this.targets.setReadOnly(state);
 this.expiry.setReadOnly(state);
 this.reason.setReadOnly(state);
 this.checkboxes.setDisabled(state);
 },

 /**
 	 * Click handler for the submit button. Does input validation, controls form state,
 	 * and show errors to the user whenever necesarry. If everything seem fine, attempt the API requests.
 	 */
 onSubmit:function(){
 varself=SpecialMassGlobalBlock,
 textWidgets=[self.targets,self.expiry,self.reason],
 enableForm=function(){
 self.disableForm(false);
 },
 showError=function(errorMsg){
 OO.ui.alert(errorMsg).done(enableForm);
 };

 self.disableForm(true);

 // Check whether all text input fields are not blank
 varblank=false;
 $.each(textWidgets,function(i,widget){
 if(widget.getValue().trim()===''){
 blank=true;
 returnfalse;
 }
 });
 if(blank){
 showError('All fields are required. Please enter valid input.');
 return;
 }

 // Split the target field input by new lines and:
 // - strip whitespace
 // - add to targets array if not already present (to avoid dupes)
 vartargets=[],
 targetLines=self.targets.getValue().split('\n');
 for(vari=0;i<targetLines.length;i++){
 varline=targetLines[i].trim();
 if(line!==''&&$.inArray(line,targets)===-1){
 targets.push(line);
 }
 }

 vartargetsCount=targets.length;
 for(i=0;i<targetsCount;i++){
 if(mw.util.isIPAddress(targets[i],true)===false){
 showError('Invalid IP address or IP range: '+targets[i]);
 return;
 }
 }

 if(targetsCount>self.MAX_LIMIT){
 showError('Maximum number of target IPs exceeded. You entered '+targetsCount+' IPs.');
 return;
 }

 varblockSettings={
 action:'globalblock',
 expiry:self.expiry.getValue(),
 reason:self.reason.getValue()
 };
 self.checkboxes.getValue().forEach(function(value){
 blockSettings[value]=true;
 });

 varprogressBar=newOO.ui.ProgressBarWidget({
 progress:0
 });
 varprogressField=newOO.ui.FieldLayout(
 progressBar,
 {label:'Progress:'}
 );
 self.$content.append(progressField.$element);

 // Initialize and start sending API requests. The requests are sent one after another.
 // If the API throws an error, this will stop sending future requests and will
 // tell the user about it.
 variterator=newself.Iterator(targets,{
 onIteration:function(me,ip,curIndex,count){
 self.doApiRequest(Object.assign(blockSettings,{target:ip}))
 .done(function(){
 progressBar.setProgress(Math.round((curIndex/count)*100));
 setTimeout(me.next,10);
 })
 .fail(function(errorMsg){
 me.error(errorMsg);
 });
 },
 onError:function(me,current,errMsg){
 progressField.$element.remove();
 showError('Error occured in API request while attempting to block '+current
 +'. Please check whether your input is valid. Script has been terminated.'
 );
 enableForm();
 },
 onComplete:function(me,last,count){
 progressBar.setProgress(100);
 OO.ui.alert('Finished. Successfully blocked '+count+' IPs.');
 }
 });
 iterator.start();

 },

 doApiRequest:function(params){
 returnthis.api.postWithToken('csrf',params)
 .then(function(){
 returntrue;
 },function(data){
 returndata;
 });
 },

 /**
 	 * Based on mw.siteMatrix.Iterator() at [[mw:User:Krinkle/Snippets/Iterate_SiteMatrix_in_JavaScript]]
 	 */
 Iterator:function(array,funcs){
 varself=this,
 arrLength=array.length,
 i,current;

 self.next=function(){
 if(i<arrLength){
 current=array[i];
 funcs.onIteration(self,current,i,arrLength);
 i++;
 }else{
 funcs.onComplete(self,current,arrLength);
 }
 };
 self.error=function(errMsg){
 console.log(current,errMsg);
 funcs.onError(self,current,errMsg);
 };
 self.start=function(){
 i=0;
 self.next();
 };
 returnself;
 }
 };

 if(mw.config.get('wgNamespaceNumber')===-1&&mw.config.get('wgTitle')==='MassGlobalBlock'&&mw.config.get('wgGlobalGroups').indexOf('steward')>-1){
 // Load dependencies conditionally as we just want those on one page only
 mw.loader.using(['oojs-ui','mediawiki.util','mediawiki.api'],function(){
 SpecialMassGlobalBlock.execute();
 });
 }elseif(mw.config.get('wgCanonicalSpecialPageName')==='GlobalBlock'){
 var$a=$('<a>')
 .attr('href',mw.config.get('wgServer')+'/wiki/Special:MassGlobalBlock')
 .text('Mass global block');
 $('#contentSub > #mw-content-subtitle > a:last-child').after(' | ',$a);
 }

AltStyle によって変換されたページ (->オリジナル) /