User:Vanderwaalforces/checkTranslationAttribution.js
Appearance
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 code will be executed when previewing this page.
This user script seems to have a documentation page at User:Vanderwaalforces/checkTranslationAttribution and an accompanying .css page at User:Vanderwaalforces/checkTranslationAttribution.css.
// @name Wikipedia translation attribution checker // @namespace https://en.wikipedia.org/ // @version 1.5.1 // @description Checks if a page is a potential unattributed translation, has suspicious access dates, or was created using the ContentTranslation tool. // @author [[User:Vanderwaalforces]] // @match https://en.wikipedia.org/wiki/* // @match https://en.wikipedia.org/w/index.php?title=* (function(){ 'use strict'; // Function to load the external CSS file functionloadCSS(href,id=''){ constlink=document.createElement('link'); link.rel='stylesheet'; link.type='text/css'; link.href=href; if(id)link.id=id;// Optional ID for the link element document.head.appendChild(link); } // Load the default CSS first loadCSS('/w/index.php?title=User:Vanderwaalforces/checkTranslationAttribution.css&action=raw&ctype=text/css','defaultCTAStyles'); // Configurable margin in days for offline work constOFFLINE_WORK_MARGIN_DAYS=7; // Ensure the script only runs in mainspace (0) or draftspace (118) constnamespace=mw.config.get('wgNamespaceNumber'); if(namespace!==0&&namespace!==118)return; constapiUrl="https://en.wikipedia.org/w/api.php"; constpageTitle=mw.config.get('wgPageName'); consttalkPageTitle="Talk:"+pageTitle; // List of valid two-, three-, five- and six-letter language codes for interwiki links constvalidLanguageCodes=[ 'aa','ab','ace','ady','af','ak','als','alt','am','ami','an','ang','ar','arc','ary','arz','as','ast', 'atj','av','avk','awa','ay','az','azb','ba','ban','bar','bat-smg','bcl','be','be-tarask','be-x-old', 'bg','bh','bi','bjn','blk','bm','bn','bo','bpy','br','bs','bug','bxr','ca','cbk-zam','cdo','ce', 'ceb','ch','cho','chr','chy','ckb','co','cr','crh','cs','csb','cu','cv','cy','da','dag','de','din', 'diq','dsb','dty','dv','dz','ee','el','eml','eo','es','et','eu','ext','fa','ff','fi','fiu-vro', 'fj','fo','fr','frp','frr','fur','fy','ga','gag','gan','gcr','gd','gl','glk','gn','gom','gor','got', 'gu','guw','gv','ha','hak','haw','he','hi','hif','ho','hr','hsb','ht','hu','hy','hyw','hz','ia', 'id','ie','ig','ii','ik','ilo','inh','io','is','it','iu','ja','jam','jbo','jv','ka','kaa','kab', 'kbd','kbp','kcg','kg','ki','kj','kk','kl','km','kn','ko','koi','kr','krc','ks','ksh','ku','kv', 'kw','ky','la','lad','lb','lbe','lez','lfn','lg','li','lij','lld','lmo','ln','lo','lrc','lt','ltg', 'lv','mad','mai','map-bms','mdf','mg','mh','mhr','mi','min','mk','ml','mn','mni','mnw','mo','mr', 'mrj','ms','mt','mus','mwl','my','myv','mzn','na','nah','nap','nds','nds-nl','ne','new','ng','nia', 'nl','nn','no','nov','nqo','nrm','nso','nv','ny','oc','olo','om','or','os','pa','pag','pam','pap', 'pcd','pcm','pdc','pfl','pi','pih','pl','pms','pnb','pnt','ps','pt','pwn','qu','rm','rmy','rn','ro', 'roa-rup','roa-tara','ru','rue','rw','sa','sah','sat','sc','scn','sco','sd','se','sg','sh','shi', 'shn','shy','si','simple','sk','skr','sl','sm','smn','sn','so','sq','sr','srn','ss','st','stq','su', 'sv','sw','szl','szy','ta','tay','tcy','te','tet','tg','th','ti','tk','tl','tn','to','tpi','tr', 'trv','ts','tt','tum','tw','ty','tyv','udm','ug','uk','ur','uz','ve','vec','vep','vi','vls','vo', 'wa','war','wo','wuu','xal','xh','xmf','yi','yo','yue','za','zea','zh','zh-classical','zh-min-nan', 'zh-yue','zu' ]; // Function to fetch edit summaries and the first revision date (first 100 revisions) functionfetchEditSummaries(){ returnnewPromise((resolve,reject)=>{ $.ajax({ url:apiUrl, data:{ action:"query", format:"json", prop:"revisions", titles:pageTitle, rvprop:"comment|timestamp", rvlimit:100, origin:"*" }, success:function(data){ constpages=data.query.pages; constrevisions=pages[Object.keys(pages)[0]].revisions; constfirstRevisionDate=revisions[revisions.length-1].timestamp; constcomments=revisions.map(rev=>rev.comment); // Log the fetched comments and first revision date for debugging console.log("Fetched edit summaries:",comments); console.log("First revision date (from 100 revisions):",firstRevisionDate); resolve({comments,firstRevisionDate}); }, error:function(err){ reject(err); } }); }); } // Function to fetch the very first revision of the article (creation edit) functionfetchFirstRevision(){ returnnewPromise((resolve,reject)=>{ $.ajax({ url:apiUrl, data:{ action:"query", format:"json", prop:"revisions", titles:pageTitle, rvprop:"comment|tags|timestamp",// Include timestamp to get the creation date rvdir:"newer",// Fetch from the oldest revision rvlimit:1,// Only fetch the first revision origin:"*" }, success:function(data){ constpages=data.query.pages; constrevisions=pages[Object.keys(pages)[0]].revisions; constfirstRevision=revisions[0];// Get the first revision console.log("First revision fetched:",firstRevision); resolve(firstRevision);// Now includes the creation timestamp }, error:function(err){ reject(err); } }); }); } // Function to fetch article wikitext for citation checks functionfetchWikitext(){ returnnewPromise((resolve,reject)=>{ $.ajax({ url:apiUrl, data:{ action:"query", format:"json", prop:"revisions", titles:pageTitle, rvprop:"content", origin:"*" }, success:function(data){ constpages=data.query.pages; constpageData=pages[Object.keys(pages)[0]]; if(pageData.revisions&&pageData.revisions[0]){ constwikitext=pageData.revisions[0]['*']; // Log the wikitext for debugging console.log("Fetched wikitext:",wikitext); resolve(wikitext); }else{ console.log("No wikitext found"); resolve(null);// Return null if wikitext is missing } }, error:function(err){ reject(err); } }); }); } // Function to check if talk page contains the word "translat" functionfetchTalkPageContent(){ returnnewPromise((resolve,reject)=>{ $.ajax({ url:apiUrl, data:{ action:"query", format:"json", prop:"revisions", titles:talkPageTitle, rvprop:"content", origin:"*" }, success:function(data){ constpages=data.query.pages; constrevisions=pages[Object.keys(pages)[0]].revisions; if(revisions&&revisions[0]&&revisions[0]['*']){ consttalkPageContent=revisions[0]['*'].toLowerCase(); // Log the talk page content for debugging console.log("Fetched talk page content:",talkPageContent); resolve(talkPageContent.includes("translat")); }else{ resolve(false); } }, error:function(err){ reject(err); } }); }); } // Helper function to check if a comment contains a valid interwiki link functioncontainsInterwikiLink(comment){ constinterwikiRegex=newRegExp(`\\b(${validLanguageCodes.join('|')}):`,'i'); returninterwikiRegex.test(comment); } // Helper function to check if an edit summary contains both "translat" or "import" and "from" functioncontainsTranslationKeywords(comment){ constlowerComment=comment.toLowerCase(); consthasTranslatAndFrom=lowerComment.includes("translat")&&lowerComment.includes("from"); consthasImportedAndFrom=lowerComment.includes("imported")&&lowerComment.includes("from"); consthasImportingAndFrom=lowerComment.includes("importing")&&lowerComment.includes("from"); // Log the status of each summary's keyword check console.log(`Summary: ${comment}, Has translat + from: ${hasTranslatAndFrom}, Has imported + from: ${hasImportedAndFrom}, Has importing + from: ${hasImportingAndFrom}`); returnhasTranslatAndFrom||hasImportedAndFrom||hasImportingAndFrom; } // Function to classify edit summaries based on the refined algorithm functionclassifyEditSummaries(editSummaries){ lethasTranslatNoInterwiki=false; lethasTranslatWithInterwiki=false; editSummaries.forEach(summary=>{ if(containsTranslationKeywords(summary)){ consthasInterwiki=containsInterwikiLink(summary); if(hasInterwiki){ hasTranslatWithInterwiki=true; }else{ hasTranslatNoInterwiki=true; } } }); return{hasTranslatNoInterwiki,hasTranslatWithInterwiki}; } // Improved date regex to handle various date formats functionparseDate(dateString){ constregexes=[ /\b(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s(\d{4})\b/,// e.g. "21 January 2024" /\b(\d{4})-(\d{2})-(\d{2})\b/,// e.g. "2024年01月21日" /\b(\d{1,2})-(\d{1,2})-(\d{4})\b/,// e.g. "21-01-2024" /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{1,2}),?\s(\d{4})\b/// e.g. "January 21, 2024" ]; for(constregexofregexes){ constmatch=regex.exec(dateString); if(match){ returnnewDate(match[0]); } } returnnull;// Return null if no valid date format is found } functionaddDays(date,days){ constresult=newDate(date); result.setDate(result.getDate()+days); returnresult; } // Function to check for suspicious access dates in the article's wikitext functioncheckSuspiciousAccessDates(wikitext,firstRevisionDate){ if(!wikitext){ console.log("No wikitext to check for access dates"); returnfalse; } constaccessDateRegex=/\|\s*access[- ]date\s*=\s*([A-Za-z0-9, -]+)/g; constmatches=[...wikitext.matchAll(accessDateRegex)]; constsuspiciousDates=[]; constfirstRevisionParsed=newDate(firstRevisionDate); constmarginDate=addDays(firstRevisionParsed,-OFFLINE_WORK_MARGIN_DAYS); matches.forEach(match=>{ constaccessDate=match[1]; constaccessDateParsed=parseDate(accessDate); if(accessDateParsed){ if(accessDateParsed<marginDate){ suspiciousDates.push(accessDate); } } }); // Log any suspicious dates for debugging console.log("Suspicious access dates:",suspiciousDates); returnsuspiciousDates.length>0; } // Function to display a message before the #contentSub element, with a dismiss button in the top-right corner functiondisplayMessage(text,className){ constmessageDiv=document.createElement("div"); messageDiv.className=`${className} cTA_banner`;// Add a general class and the specific type messageDiv.innerHTML=text;// Use innerHTML for parsing link elements constdocLink=document.createElement("a"); docLink.className="cTA_docLink"; docLink.href="https://en.wikipedia.org/wiki/User:Vanderwaalforces/checkTranslationAttribution"; docLink.target="_blank"; docLink.title="Help"; docLink.style.color="#ffffff"; docLink.innerHTML='ⓘ';// Info symbol messageDiv.appendChild(docLink); // Add dismiss button at the very top-right corner constdismissButton=document.createElement("button"); dismissButton.className="cTA_dismiss";// Use a class for the button as well dismissButton.innerHTML="✗";// ✗ symbol dismissButton.title="Close"; dismissButton.onclick=()=>{ messageDiv.style.display="none"; }; messageDiv.appendChild(dismissButton); // Insert the message before the #contentSub element to make it compatible with all skins document.querySelector('#contentSub').insertAdjacentElement('beforebegin',messageDiv); } // Main logic asyncfunctioncheckTranslationAttribution(){ try{ // Fetch the first revision to check for ContentTranslation tool and creation date constfirstRevision=awaitfetchFirstRevision(); constfirstComment=firstRevision.comment; constfirstTags=firstRevision.tags; constfirstRevisionDate=firstRevision.timestamp;// Fetch the timestamp of the first revision // Define the constants for links to WP:TFOLWP and the Translated from template constwpShortcutLink=mw.html.element('a',{ href:'/wiki/WP:TFOLWP', target:'_blank', class:'cTA_link_scut' },'WP:TFOLWP'); consttemplateLink=mw.html.element('a',{ href:'/wiki/Template:Translated_from', target:'_blank', class:'cTA_link_tplt' },'{{Translated from}}'); // If the first edit used the ContentTranslation tool, display the green banner and skip other checks if(firstComment.includes("Created by translating the page")&&firstTags.includes("contenttranslation")){ constcontentTranslationLink=mw.html.element('a',{ href:'https://www.mediawiki.org/wiki/Special:MyLanguage/Content_translation', target:'_blank', class:'cTA_link_ctl' },'ContentTranslation'); displayMessage( `This article was created using the ${contentTranslationLink} module and is correctly attributed to the source Wikipedia.`, "cTA_info_talk" ); return;// Skip all other checks if ContentTranslation tool was used } // Continue with other checks for suspicious access dates and translation attribution const{comments}=awaitfetchEditSummaries();// Use only for edit summaries now const{hasTranslatNoInterwiki,hasTranslatWithInterwiki}=classifyEditSummaries(comments); constwikitext=awaitfetchWikitext(); consthasSuspiciousAccessDates=checkSuspiciousAccessDates(wikitext,firstRevisionDate);// Use actual creation date // Display singular orange banner if there are suspicious access dates (no translation detected) if(hasSuspiciousAccessDates&&!hasTranslatNoInterwiki&&!hasTranslatWithInterwiki){ displayMessage( "Warning: There are citations in this article that have access dates from before the article was created. This suggests the article may have been copy-pasted from somewhere.", "cTA_warn_date" ); return;// Only show the orange banner and stop further checks } // If suspicious dates are found and the article is correctly attributed if(hasSuspiciousAccessDates&&hasTranslatWithInterwiki){ displayMessage( "Notice: Despite some citations having access dates before the article's creation, indicating possible copy-pasting or interwiki translation, proper attribution has been given.", "cTA_info_date" ); } // If suspicious dates are found and no proper attribution elseif(hasSuspiciousAccessDates&&hasTranslatNoInterwiki){ displayMessage( `Warning: This article is likely an unattributed translation. Please see ${wpShortcutLink} for proper attribution, and consider adding ${templateLink} to the talk page.`, "cTA_warn_unattr" ); displayMessage( "Warning: There are citations in this article that have access dates from before the article was created. This suggests the article may have been copy-pasted from somewhere.", "cTA_warn_date" ); } // If there are no suspicious dates and the article is correctly attributed elseif(!hasSuspiciousAccessDates&&hasTranslatWithInterwiki){ consthasTranslatInTalkPage=awaitfetchTalkPageContent(); if(!hasTranslatInTalkPage){ displayMessage( `Notice: This translated article has been correctly attributed. Consider optionally adding ${templateLink} to the talk page.`, "cTA_info_talk" ); }else{ displayMessage( "Notice: This translated article has been correctly attributed.", "cTA_info_talk1" ); } } // If there are no suspicious dates and no proper attribution elseif(!hasSuspiciousAccessDates&&hasTranslatNoInterwiki){ displayMessage( `Warning: This article is likely an unattributed translation. Please see ${wpShortcutLink} for proper attribution, and consider adding ${templateLink} to the talk page.`, "cTA_warn_unattr" ); } }catch(error){ console.error("Error checking translation attribution:",error); } } // Run the check checkTranslationAttribution(); })();