Jump to content
Wikipedia The Free Encyclopedia

User:Polygnotus/Scripts/ExternalLinkMonitor2.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.
Documentation for this user script can be added at User:Polygnotus/Scripts/ExternalLinkMonitor2.
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.
 /**
  * External Link Monitor for Wikipedia (Enhanced with CiteHighlighter Integration)
  * Monitors recent changes that add external links to enwiki articles
  * Filters out: bot edits, top 10k domains, users with >500 edits
  * Colors links based on CiteHighlighter source ratings
  */

 (function(){
 'use strict';

 // Add link to Tools menu
 mw.loader.using(['mediawiki.util','mediawiki.api'],function(){
 mw.util.addPortletLink(
 'p-tb',
 mw.util.getUrl('Special:BlankPage/ExternalLinkMonitor'),
 'External Link Monitor',
 't-extlinkmonitor',
 'Monitor external links added to articles'
 );
 });

 // Check if we're on the special page
 if(mw.config.get('wgCanonicalSpecialPageName')==='Blankpage'&&
 mw.config.get('wgPageName')==='Special:BlankPage/ExternalLinkMonitor'){

 $(document).ready(function(){
 initMonitor();
 });
 }

 functioninitMonitor(){
 $('#firstHeading').text('External Link Monitor');
 document.title='External Link Monitor';

 const$container=$('#mw-content-text');
 $container.html(`
  <div id="elm-container">
  <div id="elm-status">Loading source ratings and top 10k domains...</div>
  <div id="elm-controls" style="margin: 15px 0; display: none;">
  <button id="elm-start" style="padding: 8px 16px; font-size: 14px; cursor: pointer;">Start Monitoring</button>
  <button id="elm-stop" style="padding: 8px 16px; font-size: 14px; cursor: pointer; display: none;">Stop Monitoring</button>
  <span id="elm-count" style="margin-left: 15px; font-weight: bold;"></span>
  </div>
  <div id="elm-legend" style="margin: 15px 0; padding: 10px; border: 1px solid #ccc; background: #f9f9f9; display: none;">
  <strong>Color Legend:</strong>
  <span style="margin-left: 10px; padding: 3px 8px; background: limegreen;">MEDRS</span>
  <span style="margin-left: 10px; padding: 3px 8px; background: lightgreen;">Reliable</span>
  <span style="margin-left: 10px; padding: 3px 8px; background: khaki;">Caution</span>
  <span style="margin-left: 10px; padding: 3px 8px; background: lightcoral;">Unreliable</span>
  <span style="margin-left: 10px; padding: 3px 8px; background: #ffcfd5;">Preprint</span>
  <span style="margin-left: 10px; padding: 3px 8px; background: #ffb347;">Unreliable Word</span>
  </div>
  <div id="elm-results" style="margin-top: 20px; font-family: monospace; font-size: 12px;"></div>
  </div>
  `);

 loadSourceRatings();
 }

 // Storage for top 10k domains organized by first character
 constdomainsByChar={};
 // Storage for CiteHighlighter source ratings
 letsourceRatings={};
 letunreliableWords=[];
 letisMonitoring=false;
 leteventSource=null;
 leteditCount=0;

 // Color mapping from CiteHighlighter
 constcolors={
 unreliableWord:'#ffb347',
 preprint:'#ffcfd5',
 doi:'transparent',
 medrs:'limegreen',
 green:'lightgreen',
 yellow:'khaki',
 red:'lightcoral'
 };

 asyncfunctionloadSourceRatings(){
 try{
 // Load CiteHighlighter source ratings
 constapi=newmw.Api();
 constsourcesResponse=awaitapi.get({
 action:'query',
 prop:'revisions',
 titles:'User:Novem Linguae/Scripts/CiteHighlighter/SourcesJSON.js',
 rvprop:'content',
 rvslots:'main',
 formatversion:2
 });

 if(sourcesResponse.query&&sourcesResponse.query.pages&&sourcesResponse.query.pages[0]){
 constcontent=sourcesResponse.query.pages[0].revisions[0].slots.main.content;
 sourceRatings=JSON.parse(content);
 console.log('Loaded source ratings:',sourceRatings);
 }

 // Load unreliable words
 unreliableWords=getUnreliableWords();

 // Now load top 10k domains
 loadTop10kDomains();
 }catch(error){
 $('#elm-status').text('Error loading source ratings: '+error.message);
 console.error('Error loading source ratings:',error);
 }
 }

 functiongetUnreliableWords(){
 return[
 '/comment',
 'about-me',
 'about-us',
 '/about/',
 'acquire',
 'announce',
 'blog',
 'blogspot',
 'businesswire',
 'caard',
 'contact-us',
 'contactus',
 'essay',
 'fandom',
 '/forum/',
 'google.com/search',
 'innovative',
 'newswire',
 'podcast',
 '/post/',
 'preprint',
 'press-release',
 'pressrelease',
 'prnews',
 'railfan',
 'sponsored',
 'thread',
 'user-review',
 'viewtopic',
 'weebly',
 'wix',
 'wordpress',
 '/wp-'
 ];
 }

 functionloadTop10kDomains(){
 constapi=newmw.Api();

 api.get({
 action:'query',
 prop:'revisions',
 titles:'User:Polygnotus/Data/Top10kDomains',
 rvprop:'content',
 rvslots:'main',
 formatversion:2
 }).done(function(data){
 if(data.query&&data.query.pages&&data.query.pages[0]){
 constcontent=data.query.pages[0].revisions[0].slots.main.content;
 parseDomainList(content);
 $('#elm-status').text('Ready to monitor. Source ratings and top 10k domains loaded.');
 $('#elm-controls').show();
 $('#elm-legend').show();
 setupEventHandlers();
 }else{
 $('#elm-status').text('Error: Could not load domain list.');
 }
 }).fail(function(){
 $('#elm-status').text('Error: Failed to fetch domain list from API.');
 });
 }

 functionparseDomainList(content){
 // Initialize arrays for a-z and 0-9
 for(leti=0;i<26;i++){
 domainsByChar[String.fromCharCode(97+i)]=[];
 }
 domainsByChar['0-9']=[];
 domainsByChar['other']=[];

 constlines=content.split('\n');
 for(constlineoflines){
 constdomain=line.trim().toLowerCase();
 if(domain&&!domain.startsWith('#')){
 constfirstChar=domain.charAt(0);
 if(firstChar>='a'&&firstChar<='z'){
 domainsByChar[firstChar].push(domain);
 }elseif(firstChar>='0'&&firstChar<='9'){
 domainsByChar['0-9'].push(domain);
 }else{
 domainsByChar['other'].push(domain);
 }
 }
 }

 console.log('Loaded domains into character buckets:',
 Object.keys(domainsByChar).map(k=>k+': '+domainsByChar[k].length).join(', '));
 }

 functionisTop10kDomain(url){
 try{
 consturlObj=newURL(url);
 lethostname=urlObj.hostname.toLowerCase();

 // Remove www. prefix
 if(hostname.startsWith('www.')){
 hostname=hostname.substring(4);
 }

 constfirstChar=hostname.charAt(0);
 letbucket;

 if(firstChar>='a'&&firstChar<='z'){
 bucket=domainsByChar[firstChar];
 }elseif(firstChar>='0'&&firstChar<='9'){
 bucket=domainsByChar['0-9'];
 }else{
 bucket=domainsByChar['other'];
 }

 if(!bucket)returnfalse;

 // Check if hostname or any parent domain is in the list
 constparts=hostname.split('.');
 for(leti=0;i<parts.length;i++){
 consttestDomain=parts.slice(i).join('.');
 if(bucket.includes(testDomain)){
 returntrue;
 }
 }

 returnfalse;
 }catch(e){
 returnfalse;
 }
 }

 functiongetSourceRating(url){
 try{
 consturlObj=newURL(url);
 lethostname=urlObj.hostname.toLowerCase();

 // Remove www. prefix
 if(hostname.startsWith('www.')){
 hostname=hostname.substring(4);
 }

 // Check unreliable words first (highest priority in CiteHighlighter)
 consturlLower=url.toLowerCase();
 for(constwordofunreliableWords){
 if(urlLower.includes(word)){
 return{color:'unreliableWord',label:'Unreliable Word'};
 }
 }

 // Check each rating category
 constratingOrder=['red','yellow','green','medrs','doi','preprint'];
 for(constratingofratingOrder){
 if(sourceRatings[rating]){
 for(constsourceofsourceRatings[rating]){
 // Check if the domain matches
 if(hostname===source||hostname.endsWith('.'+source)){
 return{
 color:rating,
 label:rating==='medrs'?'MEDRS':
 rating==='green'?'Reliable':
 rating==='yellow'?'Caution':
 rating==='red'?'Unreliable':
 rating==='preprint'?'Preprint':
 rating==='doi'?'DOI':rating
 };
 }
 }
 }
 }

 returnnull;
 }catch(e){
 returnnull;
 }
 }

 functionsetupEventHandlers(){
 $('#elm-start').on('click',startMonitoring);
 $('#elm-stop').on('click',stopMonitoring);
 }

 functionstartMonitoring(){
 if(isMonitoring)return;

 isMonitoring=true;
 editCount=0;
 $('#elm-start').hide();
 $('#elm-stop').show();
 $('#elm-status').text('Monitoring active...');
 $('#elm-results').empty();
 updateCount();

 // Connect to Wikimedia EventStreams
 conststreamUrl='https://stream.wikimedia.org/v2/stream/mediawiki.page-links-change';
 eventSource=newEventSource(streamUrl);

 eventSource.onopen=function(){
 console.log('EventStream connection opened');
 };

 eventSource.onerror=function(e){
 console.error('EventStream error:',e);
 if(isMonitoring){
 appendResult('Connection error. Reconnecting...','error');
 }
 };

 eventSource.onmessage=function(event){
 try{
 constdata=JSON.parse(event.data);
 processChange(data);
 }catch(e){
 console.error('Error parsing event data:',e);
 }
 };
 }

 functionstopMonitoring(){
 if(!isMonitoring)return;

 isMonitoring=false;
 $('#elm-start').show();
 $('#elm-stop').hide();
 $('#elm-status').text('Monitoring stopped.');

 if(eventSource){
 eventSource.close();
 eventSource=null;
 }
 }

 functionprocessChange(data){
 // Filter: only enwiki
 constmetaUri=data.meta&&data.meta.uri;
 if(!metaUri||!metaUri.startsWith('https://en.wikipedia.org')){
 return;
 }

 // Filter: no bot edits
 constperformer=data.performer;
 if(!performer||performer.user_is_bot){
 return;
 }

 // Filter: user edit count <= 500
 constuserEditCount=performer.user_edit_count;
 if(userEditCount>500){
 return;
 }

 // Filter: only main namespace (namespace 0)
 if(data.page_namespace!==0){
 return;
 }

 // Check for added external links
 constaddedLinks=data.added_links;
 if(!addedLinks||addedLinks.length===0){
 return;
 }

 constvalidLinks=[];
 for(constlinkofaddedLinks){
 if(link.external&&link.link){
 if(!isTop10kDomain(link.link)){
 constrating=getSourceRating(link.link);
 validLinks.push({
 url:link.link,
 rating:rating
 });
 }
 }
 }

 if(validLinks.length>0){
 displayEdit(data,validLinks,performer);
 }
 }

 functiondisplayEdit(data,links,performer){
 editCount++;
 updateCount();

 constpageTitle=data.page_title||'Unknown';
 constpageTitleEncoded=encodeURIComponent(pageTitle).replace(/%20/g,'_');
 constrevId=data.rev_id||0;
 consttimestamp=data.meta&&data.meta.dt?newDate(data.meta.dt).toISOString():'';

 constuserName=performer.user_text||'Unknown';
 constuserId=performer.user_id||0;
 constuserEditCount=performer.user_edit_count||0;
 constuserRegistration=performer.user_registration_dt||'Unknown';

 lethtml='<div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; background: #f9f9f9;">';
 html+=`<div style="font-weight: bold; margin-bottom: 5px;">[${timestamp}]</div>`;
 html+=`<div><a href="https://en.wikipedia.org/wiki/${pageTitleEncoded}" target="_blank">${escapeHtml(pageTitle)}</a></div>`;
 html+=`<div><a href="https://en.wikipedia.org/w/index.php?title=${pageTitleEncoded}&diff=${revId}" target="_blank">Diff ${revId}</a></div>`;
 html+=`<div style="margin-top: 8px;">User: <a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(userName)}" target="_blank">${escapeHtml(userName)}</a> `;
 html+=`(ID: ${userId}, Edits: ${userEditCount}, Registered: ${userRegistration})</div>`;
 html+='<div style="margin-top: 8px; font-weight: bold;">Added links:</div>';
 html+='<ul style="margin: 5px 0;">';
 for(constlinkoflinks){
 constbgColor=link.rating?colors[link.rating.color]:'transparent';
 constlabel=link.rating?` [${link.rating.label}]`:'';
 // Decode the URL completely
 constdecodedUrl=decodeURIComponent(link.url);
 html+=`<li style="background-color: ${bgColor}; padding: 3px; margin: 2px 0;">`;
 html+=`<a href="${escapeHtml(decodedUrl)}" target="_blank" rel="nofollow">${escapeHtml(decodedUrl)}</a>`;
 if(label){
 html+=`<span style="font-weight: bold; margin-left: 5px;">${label}</span>`;
 }
 html+=`</li>`;
 }
 html+='</ul>';
 html+='</div>';

 $('#elm-results').prepend(html);

 // Keep only last 50 results
 constresults=$('#elm-results > div');
 if(results.length>50){
 results.slice(50).remove();
 }
 }

 functionupdateCount(){
 $('#elm-count').text(`Edits found: ${editCount}`);
 }

 functionappendResult(message,type){
 constcolor=type==='error'?'red':'green';
 $('#elm-results').prepend(`<div style="color: ${color}; margin-bottom: 10px;">${escapeHtml(message)}</div>`);
 }

 functionescapeHtml(text){
 constmap={
 '&':'&amp;',
 '<':'&lt;',
 '>':'&gt;',
 '"':'&quot;',
 "'":'&#039;'
 };
 returnString(text).replace(/[&<>"']/g,function(m){returnmap[m];});
 }

 })();

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