Jump to content
Wikimedia Meta-Wiki

MediaWiki:Gadget-globalmassblock.js

From Meta, a Wikimedia project coordination wiki

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 によって変換されたページ (->オリジナル) /