User:Polygnotus/Scripts/FindArticlesWithDuplicateRefs.js
Appearance
From Wikipedia, the free encyclopedia
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.
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 code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/FindArticlesWithDuplicateRefs.
//needed for testing DeduplicateReferences.js // Wikipedia Duplicate Reference Finder for common.js // Adds a tool to search for articles with exact duplicate references $(document).ready(function(){ // Only run on Wikipedia if(mw.config.get('wgSiteName')!=='Wikipedia')return; // Add portlet link to toolbox mw.util.addPortletLink( 'p-tb', '#', 'Find Duplicate Refs', 'find-duplicate-refs', 'Search for articles with duplicate references' ); // Add the search interface $('#find-duplicate-refs').click(function(e){ e.preventDefault(); showDuplicateRefFinder(); }); functionshowDuplicateRefFinder(){ // Remove existing dialog if present $('#duplicate-ref-finder').remove(); // Create the search interface constdialog=$(` <div id="duplicate-ref-finder" style=" position: fixed; top: 50px; right: 20px; width: 400px; background: white; border: 2px solid #0645ad; border-radius: 5px; padding: 15px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000; font-family: sans-serif; "> <h3 style="margin-top: 0; color: #0645ad;">Duplicate Reference Finder</h3> <div style="margin-bottom: 10px;"> <label>Search Method:</label><br> <input type="radio" name="searchMethod" value="random" id="method-random" checked> <label for="method-random">Random articles</label><br> <input type="radio" name="searchMethod" value="category" id="method-category"> <label for="method-category">From category</label><br> <input type="radio" name="searchMethod" value="search" id="method-search"> <label for="method-search">Search term</label> </div> <div id="search-input" style="margin-bottom: 10px; display: none;"> <input type="text" id="search-term" placeholder="Enter search term or category" style="width: 100%; padding: 5px;"> </div> <div style="margin-bottom: 10px;"> <label>Min references to check: </label> <input type="number" id="min-refs" value="10" min="5" max="100" style="width: 60px;"> </div> <div style="margin-bottom: 15px;"> <button id="start-search" style="background: #0645ad; color: white; padding: 8px 15px; border: none; border-radius: 3px; cursor: pointer;">Start Search</button> <button id="stop-search" style="background: #d33; color: white; padding: 8px 15px; border: none; border-radius: 3px; cursor: pointer; margin-left: 5px;" disabled>Stop</button> <button id="close-finder" style="float: right; background: #666; color: white; padding: 8px 15px; border: none; border-radius: 3px; cursor: pointer;">Close</button> </div> <div id="search-status" style="margin-bottom: 10px; font-weight: bold;"></div> <div id="search-progress" style="margin-bottom: 10px;"></div> <div id="results" style="max-height: 300px; overflow-y: auto; border: 1px solid #ccc; padding: 10px; background: #f9f9f9;"></div> </div> `); $('body').append(dialog); // Event handlers $('input[name="searchMethod"]').change(function(){ if($(this).val()==='random'){ $('#search-input').hide(); }else{ $('#search-input').show(); $('#search-term').attr('placeholder', $(this).val()==='category'?'Enter category name (e.g., "Living people")':'Enter search term' ); } }); $('#close-finder').click(()=>$('#duplicate-ref-finder').remove()); letsearchActive=false; letsearchAborted=false; // Make searchAborted accessible to the performSearch function window.currentSearchAborted=false; $('#start-search').click(asyncfunction(){ if(searchActive)return; searchActive=true; window.currentSearchAborted=false; $(this).prop('disabled',true); $('#stop-search').prop('disabled',false); $('#results').empty(); $('#search-status').text('Starting search...'); try{ awaitperformSearch(); }catch(error){ $('#search-status').text('Search error: '+error.message); } searchActive=false; $('#start-search').prop('disabled',false); $('#stop-search').prop('disabled',true); }); $('#stop-search').click(function(){ window.currentSearchAborted=true; $('#search-status').text('Search stopped by user.'); }); } asyncfunctionperformSearch(){ constmethod=$('input[name="searchMethod"]:checked').val(); constminRefs=parseInt($('#min-refs').val())||10; constsearchTerm=$('#search-term').val(); letarticles=[]; // Get list of articles to check if(method==='random'){ articles=awaitgetRandomArticles(50); }elseif(method==='category'&&searchTerm){ articles=awaitgetCategoryArticles(searchTerm,50); }elseif(method==='search'&&searchTerm){ articles=awaitgetSearchResults(searchTerm,50); }else{ $('#search-status').text('Please enter a search term or category.'); return; } if(articles.length===0){ $('#search-status').text('No articles found to check.'); return; } $('#search-status').text(`Checking ${articles.length} articles...`); letcheckedCount=0; letfoundCount=0; for(constarticleofarticles){ if(window.currentSearchAborted)break; checkedCount++; $('#search-progress').text(`Checked: ${checkedCount}/${articles.length} | Found: ${foundCount}`); try{ constduplicateCount=awaitcheckArticleForDuplicates(article.title,minRefs); if(duplicateCount>0){ foundCount++; addResult(article.title,duplicateCount); } }catch(error){ console.log(`Error checking ${article.title}:`,error); } // Small delay to avoid overwhelming the API awaitnewPromise(resolve=>setTimeout(resolve,200)); } $('#search-status').text(`Search complete! Checked ${checkedCount} articles, found ${foundCount} with duplicates.`); } asyncfunctiongetRandomArticles(count){ constapi=newmw.Api(); constresult=awaitapi.get({ action:'query', list:'random', rnnamespace:0,// Main namespace only rnlimit:count, format:'json' }); returnresult.query.random; } asyncfunctiongetCategoryArticles(category,count){ constapi=newmw.Api(); // Remove "Category:" prefix if present category=category.replace(/^Category:/,''); constresult=awaitapi.get({ action:'query', list:'categorymembers', cmtitle:'Category:'+category, cmnamespace:0,// Main namespace only cmlimit:count, format:'json' }); returnresult.query.categorymembers; } asyncfunctiongetSearchResults(term,count){ constapi=newmw.Api(); constresult=awaitapi.get({ action:'query', list:'search', srsearch:term, srnamespace:0,// Main namespace only srlimit:count, format:'json' }); returnresult.query.search; } asyncfunctioncheckArticleForDuplicates(title,minRefs){ constapi=newmw.Api(); // Get the article content constresult=awaitapi.get({ action:'query', titles:title, prop:'revisions', rvprop:'content', rvslots:'main', format:'json' }); constpages=result.query.pages; constpageId=Object.keys(pages)[0]; constpage=pages[pageId]; if(!page.revisions||!page.revisions[0]){ return0; } constcontent=page.revisions[0].slots.main['*']; // Extract all <ref> tags constrefRegex=/<ref[^>]*>[\s\S]*?<\/ref>/gi; constrefs=content.match(refRegex)||[]; if(refs.length<minRefs){ return0; } // Count duplicates constrefCounts={}; letduplicateCount=0; refs.forEach(ref=>{ // Normalize whitespace for comparison constnormalizedRef=ref.replace(/\s+/g,' ').trim(); if(refCounts[normalizedRef]){ refCounts[normalizedRef]++; if(refCounts[normalizedRef]===2){ duplicateCount++;// First time we see a duplicate } }else{ refCounts[normalizedRef]=1; } }); returnduplicateCount; } functionaddResult(title,duplicateCount){ consteditUrl=`/w/index.php?title=${encodeURIComponent(title)}&action=edit`; constviewUrl=`/wiki/${encodeURIComponent(title)}`; constresultHtml=` <div style="margin-bottom: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 3px; background: white;"> <strong><a href="${viewUrl}" target="_blank">${mw.html.escape(title)}</a></strong><br> <span style="color: #d33;">${duplicateCount} duplicate reference${duplicateCount>1?'s':''}</span><br> <a href="${editUrl}" target="_blank" style="color: #0645ad;">Edit article</a> </div> `; $('#results').append(resultHtml); // Auto-scroll to bottom constresultsDiv=$('#results')[0]; resultsDiv.scrollTop=resultsDiv.scrollHeight; } });