MediaWiki:Gadget-globalmassblock.js
Appearance
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); }