// @name Wikipedia translation attribution checker
// @namespace https://en.wikipedia.org/
// @version 1.3
// @description Checks if a page is a potential unattributed translation or not, considering interwiki links in edit summaries.
// @author User:Vanderwaalforces
// @match https://en.wikipedia.org/wiki/*
// @match https://en.wikipedia.org/w/index.php?title=*
(function() {
'use strict';
// Ensure the script only runs in mainspace (0) or draftspace (118)
const namespace = mw.config.get('wgNamespaceNumber');
if (namespace !== 0 && namespace !== 118) return;
const apiUrl = "https://en.wikipedia.org/w/api.php";
const pageTitle = mw.config.get('wgPageName');
const talkPageTitle = "Talk:" + pageTitle;
// List of valid two-letter and three-letter language codes for interwiki links
const validLanguageCodes = [
'en', 'de', 'fr', 'es', 'ru', 'ha', 'he', 'ig', 'it', 'ja', 'zh', 'ar', 'pt', 'nl', 'pl', 'fa', 'fi', 'sv', 'no', 'da', 'cs', 'ko',
'ace', 'arc', 'arz', 'ast', 'bat', 'bcl', 'bjn', 'bpy', 'bug', 'cbk', 'ceb', 'crh', 'csb', 'diq', 'dsb', 'eml',
'fiu', 'gag', 'glk', 'hif', 'hsb', 'ilo', 'jv', 'kab', 'kbd', 'ksh', 'lez', 'lmo', 'ltg', 'mai', 'map', 'mhr',
'min', 'mrj', 'mwl', 'nds', 'nov', 'nrm', 'pag', 'pam', 'pcm', 'pdc', 'pfl', 'pnb', 'roa', 'rue', 'sah', 'scn', 'skr',
'srn', 'szl', 'tpi', 'vec', 'vep', 'vls', 'war', 'wuu', 'xmf', 'yo', 'yue', 'zea'
];
// Function to fetch edit summaries and the first revision date
function fetchEditSummaries() {
return new Promise((resolve, reject) => {
$.ajax({
url: apiUrl,
data: {
action: "query",
format: "json",
prop: "revisions",
titles: pageTitle,
rvprop: "comment|timestamp",
rvlimit: 10,
origin: "*"
},
success: function(data) {
const pages = data.query.pages;
const revisions = pages[Object.keys(pages)[0]].revisions;
const firstRevisionDate = revisions[revisions.length - 1].timestamp;
const comments = 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:", firstRevisionDate);
resolve({ comments, firstRevisionDate });
},
error: function(err) {
reject(err);
}
});
});
}
// Function to fetch article wikitext for citation checks
function fetchWikitext() {
return new Promise((resolve, reject) => {
$.ajax({
url: apiUrl,
data: {
action: "query",
format: "json",
prop: "revisions",
titles: pageTitle,
rvprop: "content",
origin: "*"
},
success: function(data) {
const pages = data.query.pages;
const pageData = pages[Object.keys(pages)[0]];
if (pageData.revisions && pageData.revisions[0]) {
const wikitext = 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"
function fetchTalkPageContent() {
return new Promise((resolve, reject) => {
$.ajax({
url: apiUrl,
data: {
action: "query",
format: "json",
prop: "revisions",
titles: talkPageTitle,
rvprop: "content",
origin: "*"
},
success: function(data) {
const pages = data.query.pages;
const revisions = pages[Object.keys(pages)[0]].revisions;
if (revisions && revisions[0] && revisions[0]['*']) {
const talkPageContent = 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
function containsInterwikiLink(comment) {
const interwikiRegex = new RegExp(`\\b(${validLanguageCodes.join('|')}):`, 'i');
return interwikiRegex.test(comment);
}
// Helper function to check if an edit summary contains both "translat" or "import" and "from"
function containsTranslationKeywords(comment) {
const lowerComment = comment.toLowerCase();
const hasTranslatAndFrom = lowerComment.includes("translat") && lowerComment.includes("from");
const hasImportAndFrom = lowerComment.includes("import") && lowerComment.includes("from");
// Log the status of each summary's keyword check
console.log(`Summary: ${comment}, Has translat + from: ${hasTranslatAndFrom}, Has import + from: ${hasImportAndFrom}`);
return hasTranslatAndFrom || hasImportAndFrom;
}
// Function to classify edit summaries based on the refined algorithm
function classifyEditSummaries(editSummaries) {
let hasTranslatNoInterwiki = false;
let hasTranslatWithInterwiki = false;
editSummaries.forEach(summary => {
if (containsTranslationKeywords(summary)) {
const hasInterwiki = containsInterwikiLink(summary);
if (hasInterwiki) {
hasTranslatWithInterwiki = true;
} else {
hasTranslatNoInterwiki = true;
}
}
});
return { hasTranslatNoInterwiki, hasTranslatWithInterwiki };
}
// Function to check for suspicious access dates in the article's wikitext
function checkSuspiciousAccessDates(wikitext, firstRevisionDate) {
if (!wikitext) {
console.log("No wikitext to check for access dates");
return false;
}
const accessDateRegex = /\|\s*access[- ]date\s*=\s*([0-9]{1,2}\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s[0-9]{4}|[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{2}-[0-9]{2}-[0-9]{4}|[A-Za-z]+\s[0-9]{1,2},?\s[0-9]{4})/g;
const matches = [...wikitext.matchAll(accessDateRegex)];
const suspiciousDates = [];
matches.forEach(match => {
const accessDate = match[1];
const accessDateParsed = new Date(accessDate);
const firstRevisionParsed = new Date(firstRevisionDate);
if (!isNaN(accessDateParsed.getTime()) && accessDateParsed < firstRevisionParsed) {
suspiciousDates.push(accessDate);
}
});
// Log any suspicious dates for debugging
console.log("Suspicious access dates:", suspiciousDates);
return suspiciousDates.length > 0;
}
// Function to display a message before the #contentSub element, with a dismiss button in the top-right corner
function displayMessage(text, color) {
const messageDiv = document.createElement("div");
messageDiv.style.backgroundColor = color;
messageDiv.style.color = "white";
messageDiv.style.padding = "15px";
messageDiv.style.textAlign = "center";
messageDiv.style.fontSize = "16px";
messageDiv.style.fontWeight = "bold";
messageDiv.style.position = "relative"; // Required for dismiss button positioning
messageDiv.textContent = text; // Set the message text
// Add dismiss button at the very top-right corner
const dismissButton = document.createElement("button");
dismissButton.textContent = "x";
dismissButton.style.position = "absolute";
dismissButton.style.top = "5px";
dismissButton.style.right = "10px";
dismissButton.style.backgroundColor = "#ff5f5f";
dismissButton.style.border = "none";
dismissButton.style.color = "white";
dismissButton.style.padding = "5px 10px";
dismissButton.style.cursor = "pointer";
dismissButton.style.fontWeight = "bold";
dismissButton.style.borderRadius = "5px";
dismissButton.onclick = () => {
messageDiv.style.display = "none";
};
messageDiv.appendChild(dismissButton);
// Insert the message before the #contentSub element to make it compatible with all skins
$('#contentSub').before(messageDiv);
}
// Main logic
async function checkTranslationAttribution() {
try {
// Fetch edit summaries and classify them
const { comments, firstRevisionDate } = await fetchEditSummaries();
const { hasTranslatNoInterwiki, hasTranslatWithInterwiki } = classifyEditSummaries(comments);
// Fetch article wikitext and check for suspicious access dates
const wikitext = await fetchWikitext();
const hasSuspiciousAccessDates = checkSuspiciousAccessDates(wikitext, firstRevisionDate);
// Logic to handle combinations of correct attribution and suspicious access dates
// 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, proper attribution has been given.", "green");
}
// If suspicious dates are found and no proper attribution
else if (hasSuspiciousAccessDates && hasTranslatNoInterwiki) {
displayMessage("Warning: This article is likely an unattributed translation. Please see [[WP:TFOLWP]] for proper attribution, and consider adding {{Translated from}} to the talk page.", "red");
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.", "orange");
}
// If there are no suspicious dates and the article is correctly attributed
else if (!hasSuspiciousAccessDates && hasTranslatWithInterwiki) {
const hasTranslatInTalkPage = await fetchTalkPageContent();
if (!hasTranslatInTalkPage) {
displayMessage("Notice: This translated article has been correctly attributed. Consider optionally adding {{Translated page}} to the talk page.", "green");
} else {
displayMessage("Notice: This translated article has been correctly attributed.", "green");
}
}
// If there are no suspicious dates and no proper attribution
else if (!hasSuspiciousAccessDates && hasTranslatNoInterwiki) {
displayMessage("Warning: This article is likely an unattributed translation. Please see [[WP:TFOLWP]] for proper attribution, and consider adding {{Translated from}} to the talk page.", "red");
}
} catch (error) {
console.error("Error checking translation attribution:", error);
}
}
// Run the check
checkTranslationAttribution();
})();