User:Vanderwaalforces/checkTranslationAttribution.js

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.
// @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();
})();