Jump to content
Wikipedia The Free Encyclopedia

User:Novem Linguae/Scripts/UnblockReview.js

From Wikipedia, the free encyclopedia
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This user script seems to have a documentation page at User:Novem Linguae/Scripts/UnblockReview.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
 // <nowiki>

 // === Compiled with Novem Linguae's publish.php script ======================

 // === modules/UnblockReview.js ======================================================

 classUnblockReview{
 constructor(){
 this.SIGNATURE='~~~~';
 }

 /**
 	 * Process the accept or decline of an unblock request.
 	 *
 	 * @param {string} wikitext - The wikitext of the page.
 	 * @param {string} paramsAndReason - The parameters and reason of the unblock request, e.g.
 	 * "NewUsername|Reason" or "Reason". The initial pipe is omitted.
 	 * @param {string} acceptDeclineReason - The reason for accepting or declining the unblock request.
 	 * @param {string} DEFAULT_DECLINE_REASON - The default reason for declining the unblock request.
 	 * @param {string} acceptOrDecline - Either "accept" or "decline".
 	 * @return {string} wikitext
 	 */
 processAcceptOrDecline(wikitext,paramsAndReason,acceptDeclineReason,DEFAULT_DECLINE_REASON,acceptOrDecline){
 // HTML does one line break and wikitext does 2ish. Cut off all text after the first line break to avoid breaking our search algorithm.
 paramsAndReason=paramsAndReason.split('\n')[0];

 letinitialText='';
 // Special case: If the user didn't provide a reason, the template will display "Please provide a reason as to why you should be unblocked", and this will be detected as the appealReason.
 constreasonIsProvided=!paramsAndReason.startsWith('Please provide a reason as to why you should be unblocked');
 if(!reasonIsProvided){
 initialText=wikitext.match(/(\{\{Unblock)\}\}/i)[1];
 paramsAndReason='';
 }else{
 initialText=this.getLeftHalfOfUnblockTemplate(wikitext,paramsAndReason);
 }

 if(!acceptDeclineReason.trim()){
 acceptDeclineReason=DEFAULT_DECLINE_REASON+' '+this.SIGNATURE;
 }elseif(!this.hasSignature(acceptDeclineReason)){
 acceptDeclineReason=acceptDeclineReason+' '+this.SIGNATURE;
 }

 // eslint-disable-next-line no-useless-concat
 constnegativeLookbehinds='(?<!<'+'nowiki>)';
 constregEx=newRegExp(negativeLookbehinds+this.escapeRegExp(initialText+paramsAndReason),'g');
 consttemplateName=initialText.match(/^\{\{([A-Za-z-]+)/i)[1];
 letwikitext2=wikitext.replace(
 regEx,
 '{{'+templateName+' reviewed|'+acceptOrDecline+'='+acceptDeclineReason+'|'+paramsAndReason
 );

 if(wikitext===wikitext2){
 thrownewError('Replacing text with unblock message failed!');
 }

 // get rid of any [#*:] in front of {{unblock X}} templates. indentation messes up the background color and border of the unblock template.
 wikitext2=wikitext2.replace(/^[#*: ]{1,}(\{\{\s*unblock)/mi,'1ドル');

 returnwikitext2;
 }

 /**
 	 * Given the wikitext of an entire page, and the |reason= parameter of one of the many unblock templates (e.g. {{Unblock}}, {{Unblock-un}}, {{Unblock-auto}}, {{Unblock-bot}}, etc.), return the wikitext of just the beginning of the template.
 	 *
 	 * For example, "Test {{unblock|reason=Your reason here [[User:Filipe46]]}} Test" as the wikitext and "Your reason here" as the appealReason will return "{{unblock|reason=".
 	 *
 	 * This can also handle 1=, and no parameter at all (just a pipe)
 	 */
 getLeftHalfOfUnblockTemplate(wikitext,appealReason){
 // Isolate the reason, stripping out all template syntax. So `{{Unblock|reason=ABC}}` becomes matches = [ 'ABC ']
 // eslint-disable-next-line no-useless-concat
 constnegativeLookbehinds='(?<!<'+'nowiki>{{unblock\\|reason=)(?<!reviewed ?\\|1=)';
 constregEx=newRegExp(negativeLookbehinds+this.escapeRegExp(appealReason),'g');
 letmatches=wikitext.matchAll(regEx);
 matches=[...matches];

 if(matches.length===0){
 thrownewError('Searching for target text failed!');
 }

 // Loop through all the potential matches, trying to find an {{Unblock template. If found, return the beginning of the template.
 for(constmatchofmatches){
 constmatchPos=match.index;
 letunblockTemplateStartPos;

 // Scan backwards from the match until we find {{
 // Stop at the beginning of the string OR after 50 characters
 conststopPos=Math.max(0,matchPos-50);
 for(leti=matchPos;i>stopPos;i--){
 if(wikitext[i]==='{'&&wikitext[i-1]==='{'){
 unblockTemplateStartPos=i-1;
 break;
 }
 }

 // Don't match stuff that isn't an unblock template
 constinitialText=wikitext.slice(unblockTemplateStartPos,matchPos);
 if(!initialText.match(/^\{\{unblock/i)){
 continue;
 }

 returninitialText;
 }

 thrownewError('Searching backwards failed!');
 }

 /**
 	 * @copyright coolaj86, CC BY-SA 4.0, https://stackoverflow.com/a/6969486/3480193
 	 */
 escapeRegExp(string){
 // $& means the whole matched string
 returnstring.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
 }

 /**
 	 * Is there a signature (four tildes) present in the given text, outside of a nowiki element?
 	 */
 hasSignature(text){
 // no literal signature?
 if(!text.includes(this.SIGNATURE)){
 returnfalse;
 }

 // if there's a literal signature and no nowiki elements,
 // there must be a real signature
 if(!text.includes('')){
 returntrue;
 }

 // Save all nowiki spans
 constnowikiSpanStarts=[];// list of ignored span beginnings
 constnowikiSpanLengths=[];// list of ignored span lengths
 constNOWIKI_RE=/.*?<\/nowiki>/g;
 letspanMatch;
 do{
 spanMatch=NOWIKI_RE.exec(text);
 if(spanMatch){
 nowikiSpanStarts.push(spanMatch.index);
 nowikiSpanLengths.push(spanMatch[0].length);
 }
 }while(spanMatch);

 // So that we don't check every ignore span every time
 letnowikiSpanStartIdx=0;

 constSIG_RE=newRegExp(this.SIGNATURE,'g');
 letsigMatch;

 matchLoop:
 do{
 sigMatch=SIG_RE.exec(text);
 if(sigMatch){
 // Check that we're not inside a nowiki
 for(letnwIdx=nowikiSpanStartIdx;nwIdx<
 nowikiSpanStarts.length;nwIdx++){
 if(sigMatch.index>nowikiSpanStarts[nwIdx]){
 if(sigMatch.index+sigMatch[0].length<=
 nowikiSpanStarts[nwIdx]+nowikiSpanLengths[nwIdx]){

 // Invalid sig
 continuematchLoop;
 }else{
 // We'll never encounter this span again, since
 // headers only get later and later in the wikitext
 nowikiSpanStartIdx=nwIdx;
 }
 }
 }

 // We aren't inside a nowiki
 returntrue;
 }
 }while(sigMatch);
 returnfalse;
 }
 }
 $(asyncfunction(){

 // === main.js ======================================================

 /*
 Forked from [[User:Enterprisey/unblock-review.js]] on Oct 31, 2024.
 Many additional bugs fixed.
 */

 /* global importStylesheet */
 // 
 (asyncfunction(){
 constUNBLOCK_REQ_COLOR_PRE_2025='rgb(235, 244, 255)';
 constUNBLOCK_REQ_COLOR_POST_2025='var(--background-color-progressive-subtle, #EBF4FF)';
 constDEFAULT_DECLINE_REASON='{{subst:Decline reason here}}';
 constADVERT=' ([[User:Novem Linguae/Scripts/UnblockReview.js|unblock-review]])';

 asyncfunctionexecute(){
 constuserTalkNamespace=3;
 if(mw.config.get('wgNamespaceNumber')!==userTalkNamespace){
 return;
 }

 $.when($.ready,mw.loader.using(['mediawiki.api','mediawiki.util'])).then(async()=>{
 // add styles
 mw.util.addCSS(
 '.unblock-review td { padding: 0 }'+
 'td.reason-container { padding-right: 1em; width: 30em }'+
 '.unblock-review-reason { height: 5em }');
 importStylesheet('User:Enterprisey/mw-ui-button.css');
 importStylesheet('User:Enterprisey/mw-ui-input.css');

 // look for user-block HTML class, which will correspond to {{Unblock}} requests
 constuserBlockBoxes=document.querySelectorAll('div.user-block');
 for(leti=0,n=userBlockBoxes.length;i<n;i++){
 if(
 userBlockBoxes[i].style['background-color']!==UNBLOCK_REQ_COLOR_PRE_2025&&
 userBlockBoxes[i].style.background!==UNBLOCK_REQ_COLOR_POST_2025
 ){
 continue;
 }

 // We now have a pending unblock request - add UI
 constunblockDiv=userBlockBoxes[i];
 const[container,hrEl]=addTextBoxAndButtons(unblockDiv);
 awaitlistenForAcceptAndDecline(container,hrEl);
 }
 });
 }

 functionaddTextBoxAndButtons(unblockDiv){
 constcontainer=document.createElement('table');
 container.className='unblock-review';
 // Note: The innerHtml of the button is sensitive. Is used to figure out which accept/decline wikitext to use. Don't add whitespace to it.
 container.innerHTML=`
 			<tr>
 				<td class='reason-container' rowspan='2'>
 					<textarea class='unblock-review-reason mw-ui-input' placeholder='Reason for accepting/declining here'>${DEFAULT_DECLINE_REASON}</textarea>
 				</td>
 				<td>
 					<button class='unblock-review-accept mw-ui-button mw-ui-progressive'>Accept</button>
 				</td>
 			</tr>
 			<tr>
 				<td>
 					<button class='unblock-review-decline mw-ui-button mw-ui-destructive'>Decline</button>
 				</td>
 			</tr>`;
 consthrEl=unblockDiv.querySelector('hr');
 unblockDiv.insertBefore(container,hrEl.previousElementSibling);
 return[container,hrEl];
 }

 asyncfunctionlistenForAcceptAndDecline(container,hrEl){
 constreasonArea=container.querySelector('textarea');
 $(container).find('button').on('click',asyncfunction(){
 // look at the innerHtml of the button to see if it says "Accept" or "Decline"
 constacceptOrDecline=$(this).text().toLowerCase();
 constappealReason=hrEl.nextElementSibling.nextElementSibling.childNodes[0].textContent;
 // FIXME: should handle this case (|reason=\nText, https://github.com/NovemLinguae/UserScripts/issues/240) instead of throwing an error
 if(appealReason==='\n'){
 mw.notify('UnblockReview error 1: unable to find decline reason by scanning HTML',{type:'error'});
 return;
 }

 consttitle=mw.config.get('wgPageName');
 constwikitext=awaitgetWikitext(title);

 // change wikitext
 // eslint-disable-next-line no-undef
 constunblockReview=newUnblockReview();
 constacceptDeclineReason=reasonArea.value;
 constwikitext2=unblockReview.processAcceptOrDecline(
 wikitext,
 appealReason,
 acceptDeclineReason,
 DEFAULT_DECLINE_REASON,
 acceptOrDecline
 );
 if(wikitext===wikitext2){
 mw.notify('UnblockReview error 2: unable to determine write location.',{type:'error'});
 return;
 }

 constacceptingOrDeclining=(acceptOrDecline==='accept'?'Accepting':'Declining');
 consteditSummary=acceptingOrDeclining+' unblock request'+ADVERT;
 awaiteditPage(title,wikitext2,editSummary);
 window.location.reload(true);
 });
 }

 asyncfunctiongetWikitext(title){
 constdata=await(newmw.Api()).get({
 format:'json',
 action:'query',
 prop:'revisions',
 rvprop:'content',
 rvlimit:1,
 titles:title
 });
 constpageId=Object.keys(data.query.pages)[0];
 constwikitext=data.query.pages[pageId].revisions[0]['*'];
 returnwikitext;
 }

 asyncfunctioneditPage(title,wikitext,editSummary){
 await(newmw.Api()).postWithToken('csrf',{
 action:'edit',
 title:title,
 summary:editSummary,
 text:wikitext
 });
 }

 awaitexecute();
 }());
 // 


 });

 // </nowiki>

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