Jump to content

User:Rusalkii/RfDInfo.js

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.
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.
// <nowiki>
(function() {
    'use strict';
    
    const API_BASE = 'https://en.wikipedia.org/w/api.php';

    async function fetchAPI(params) {
        const url = `${API_BASE}?format=json&origin=*&${params}`;
        const response = await fetch(url);
        return response.json();
    }

    async function getPageInfo(title) {
        const data = await fetchAPI(`action=query&prop=info|linkshere|revisions&inprop=&lhlimit=500&rvlimit=1&rvdir=newer&rvprop=timestamp&titles=${encodeURIComponent(title)}`);
        const pageId = Object.keys(data.query.pages)[0];
        return data.query.pages[pageId];
    }

    async function getPageContent(title) {
        const data = await fetchAPI(`action=query&prop=revisions&rvprop=content&titles=${encodeURIComponent(title)}`);
        const page = Object.values(data.query.pages)[0];
        return page.revisions?.[0]?.['*'] || '';
    }

    async function getRedirectTarget(content) {
        const match = content.match(/#REDIRECT\s*\[\[(.*?)\]\]/i);
        return match ? match[1] : null;
    }

    function formatCreationDate(timestamp) {
        const creationDate = new Date(timestamp);
        const ageYears = (new Date() - creationDate) / (365.25 * 24 * 60 * 60 * 1000);
        
        let formatted = ageYears < 1 
            ? creationDate.toLocaleDateString(undefined, { month: 'long', day: 'numeric', year: 'numeric' })
            : creationDate.getFullYear().toString();
        
        if (ageYears > 10) {
            formatted = `<span style="color: red;">${formatted}</span>`;
        }
        
        return formatted;
    }

    async function checkTextMentioned(targetTitle, redirectTitle) {
        const content = await getPageContent(targetTitle);
        const cleanContent = content.replace(/<ref[^>]*>.*?<\/ref>|<ref[^>]*\/>/g, '');
        return cleanContent.includes(redirectTitle);
    }

	async function getRedirectHistory(title) {
	    const data = await fetchAPI(`action=query&prop=revisions&titles=${encodeURIComponent(title)}&rvprop=content|timestamp|ids&rvlimit=max`);
	    const page = Object.values(data.query.pages)[0];
	    
	    if (!page?.revisions) return {};
	    
	    const targets = new Map();
	    let lastNonRedirectRev = null;
	    let firstRedirectYear = null;
	    let firstRedirectRev = null;
	    
	    // Revisions are newest first, so iterate backwards
	    for (let i = page.revisions.length - 1; i >= 0; i--) {
	        const rev = page.revisions[i];
	        const isRedirect = /#REDIRECT/i.test(rev['*']);
	        
	        if (isRedirect) {
	            const redirectMatch = rev['*'].match(/#REDIRECT\s*\[\[(.*?)\]\]/i);
	            if (redirectMatch) {
	                const year = new Date(rev.timestamp).getFullYear();
	                targets.set(redirectMatch[1], year);
	                if (lastNonRedirectRev && !firstRedirectYear) {
	                	firstRedirectRev = rev.revid;
	                    firstRedirectYear = year;
	                }
                }
	        } else {
	            lastNonRedirectRev = rev.revid;
	        }
	    }
	    
	    return {
	        history: targets.size > 1 ? Array.from(targets.entries())
	            .map(([target, year]) => `${target} (${year})`)
	            .join(' → ') : null,
	        significantHistory: firstRedirectRev && firstRedirectYear ? {
	            revid: firstRedirectRev,
	            redirectYear: firstRedirectYear
	        } : null
	    };
	}

    function extractRedirectTemplates(content) {
        const matches = content.match(/\{\{(?:R(?:edir(?:ect)?)?[ _](?!category\s*shell)|Redirect[ _]from)[^}|]*(?:\|[^}]*)?\}\}/gi) || [];
        return matches.map(template => template.replace(/\{\{([^}|]+).*?\}\}/i, '$1').trim());
    }

    async function getTalkPageInfo(title) {
        const talkContent = await getPageContent(`Talk:${title}`);
        if (!talkContent) return {};
        
        const rfdMatches = talkContent.match(/\{\{old ?rfd[^\}]*?\|\s*result\s*=\s*'''(.*?)'''.*}}/gi) || [];
        const afdMatches = talkContent.match(/\{\{old\s*[ax]fd(?:\s+multi|\s*full)?[^\}]*?\|\s*result\s*=\s*'''(.*?)'''.*}}/gi) || [];
        const mergeMatches = talkContent.match(/\{\{merged[-_ ]?(from|to)[^\}]*?\}\}/gi) || [];
        
	    return {
	        rfdHistory: rfdMatches.map(match => {
	            const pageMatch = match.match(/\|\s*page\s*=\s*([^|}\n]+)/i);
	            const resultMatch = match.match(/\|\s*result\s*=\s*'''(.*?)'''/i);
	            const page = pageMatch[1].trim();
	            const result = resultMatch[1].trim();
	            return `<a href="https://en.wikipedia.org/wiki/Wikipedia:Redirects_for_discussion/Log/${page}" target="_blank">${result}</a>`;
	        }),
	        afdHistory: afdMatches.map(match => {
	            const pageMatch = match.match(/\|\s*page\s*=\s*([^|}\n]+)/i);
	            const resultMatch = match.match(/\|\s*result\s*=\s*'''(.*?)'''/i);
	            const page = pageMatch ? pageMatch[1].trim() : title;
	            const result = resultMatch[1].trim();
	            return `<a href="https://en.wikipedia.org/wiki/Wikipedia:Articles_for_deletion/${page}" target="_blank">${result}</a>`;
	        }),
	        mergeProposals: mergeMatches.length
	    };
    }

    async function getMoveHistory(title) {
        const data = await fetchAPI(`action=query&list=logevents&letype=move&letitle=${encodeURIComponent(title)}&leprop=title|details|timestamp`);
        const events = data.query.logevents;
        
        if (!events?.length) return null;
        
        const moves = events.map(event => 
            `${event.params.target_title} (${new Date(event.timestamp).getFullYear()})`
        );
        const firstYear = new Date(events[events.length - 1].timestamp).getFullYear();
        moves.unshift(`${events[events.length - 1].title} (${firstYear})`);
        
        return moves.join(' → ');
    }

    async function getPageViews(title) {
        const end = new Date();
        const start = new Date();
        start.setDate(start.getDate() - 30);
        const formatDate = date => date.toISOString().slice(0, 10).replace(/-/g, '');
        
        const url = `/media/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/user/${encodeURIComponent(title)}/daily/${formatDate(start)}/${formatDate(end)}`;
        const response = await fetch(url);
        
        if (!response.ok) return 0;
        
        const data = await response.json();
        return data.items?.reduce((sum, item) => sum + item.views, 0) || 0;
    }

    async function fetchRedirectInfo(title) {
        const info = { errors: [] };
        
        try {
            const pageInfo = await getPageInfo(title);
            
            if (pageInfo.missing) {
                info.errors.push('Page not found');
                return info;
            }
            
            // Creation date
            if (pageInfo.revisions?.[0]?.timestamp) {
                info.created = formatCreationDate(pageInfo.revisions[0].timestamp);
            }
            
            // Incoming links
            info.incomingLinks = pageInfo.linkshere 
                ? pageInfo.linkshere.filter(link => [0, 12, 100, 126].includes(link.ns)).length // Main, help, portal, MOS
                : 0;
            
            // Redirect content analysis
            const content = await getPageContent(title);
            const targetTitle = await getRedirectTarget(content);
            
            if (targetTitle) {
                info.targetTitle = targetTitle;
                info.textMentioned = await checkTextMentioned(targetTitle, title);
            }
            
            // Redirect history
            const { history, significantHistory } = await getRedirectHistory(title);
            info.redirectHistory = history;
            info.significantHistory = significantHistory;
            
            // Redirect templates
            info.redirectTemplates = extractRedirectTemplates(content);
            
            // Talk page info (RfDs, AfDs, merge proposals)
            Object.assign(info, await getTalkPageInfo(title));
            
            // Move history
            info.moveHistory = await getMoveHistory(title);
            
            // Page views
            info.views = await getPageViews(title);
            
        } catch (error) {
            info.errors.push(error.message);
        }
        
        return info;
    }

    function createInfoButton(redirectLink) {
        const button = document.createElement('button');
        button.textContent = 'ℹ️';
        button.style.cssText = 'margin-left: 5px; font-size: 12px; padding: 2px 5px; cursor: pointer;';
        
        const title = redirectLink.title;
        
        button.addEventListener('click', async (e) => {
            e.preventDefault();
            e.stopPropagation();
            
            button.disabled = true;
            button.textContent = '⌛';
            
            const info = await fetchRedirectInfo(title);
            
			let infoText = `<strong>${title}</strong>`;
			infoText += ` <small>(<a href="https://en.wikipedia.org/w/index.php?fulltext=1&search=%22${encodeURIComponent(title)}%22&title=Special%3ASearch&ns0=1" target="_blank">Wikipedia</a> | <a href="https://www.google.com/search?q=%22${encodeURIComponent(title)}%22" target="_blank">Google</a>)</small><div style="margin-bottom: 8px;"></div>`;

			infoText += info.created ? `<b>Created:</b> ${info.created}<br>` : '';
			infoText += info.incomingLinks > 0 ? `<b>Incoming links:</b> ${info.incomingLinks}<br>` : '';
			infoText += info.views !== undefined ? `<b>Views (30d):</b> ${info.views}<br>` : '';
			infoText += info.textMentioned !== undefined ? `<b>Mentioned in target:</b> ${info.textMentioned ? `<a href="https://en.wikipedia.org/wiki/${encodeURIComponent(info.targetTitle)}#:~:text=${title}" target="_blank">Yes</a>` : 'No'}<br>` : '';
			infoText += info.redirectTemplates?.length ? `<b>Tags:</b> ${info.redirectTemplates.join(', ')}<br>` : '';
			infoText += info.rfdHistory?.length ? `<b>Past RfDs:</b> ${info.rfdHistory.join(', ')}<br>` : '';
			infoText += info.afdHistory?.length ? `<b>Past AfDs:</b> ${info.afdHistory.join(', ')}<br>` : '';
			infoText += info.mergeProposals ? `<b>Merge proposals:</b> ${info.mergeProposals}<br>` : '';
			infoText += info.moveHistory ? `<b>Move history:</b> ${info.moveHistory}<br>` : '';
			infoText += info.redirectHistory ? `<b>Target history:</b> ${info.redirectHistory}<br>` : '';
			infoText += info.significantHistory ? `<b>BLARed:</b> <a href="https://en.wikipedia.org/w/index.php?diff=${info.significantHistory.revid}" target="_blank">Last non-redirect revision</a> (${info.significantHistory.redirectYear})<br>` : '';

            if (info.errors.length > 0) {
                infoText += `Errors: ${info.errors.join(', ')}`;
            }
            
            const existingPopup = document.querySelector('.rfd-info-popup');
            if (existingPopup) existingPopup.remove();
            
			const popup = document.createElement('div');
			popup.className = 'rfd-info-popup';
			popup.style.cssText = 'position: fixed; background: #fff; border: 1px solid #aaa; padding: 15px; z-index: 999999; box-shadow: 2px 2px 5px rgba(0,0,0,0.2); font-size: 13px; line-height: 1.5; left: 50%; top: 50%; transform: translate(-50%, -50%); max-width: 400px; max-height: 80vh; overflow-y: auto;';            
			popup.innerHTML = infoText;
            
            document.body.appendChild(popup);
            
            const closePopup = (e) => {
                if (!popup.contains(e.target) && e.target !== button) {
                    popup.remove();
                    document.removeEventListener('click', closePopup);
                }
            };
            
            setTimeout(() => {
                document.addEventListener('click', closePopup);
            }, 100);
            
            button.disabled = false;
            button.textContent = 'ℹ️';
        });
        
        return button;
    }

    function addButtons() {
        const redirectLinks = document.querySelectorAll('a.deletion');
        
        redirectLinks.forEach((link) => {
            if (!link.previousElementSibling || !link.previousElementSibling.classList.contains('rfd-info-button')) {
                const infoButton = createInfoButton(link);
                infoButton.classList.add('rfd-info-button');
                link.insertAdjacentElement('beforebegin', infoButton);
            }
        });
    }

	// Do not run anywhere except RfD pages
    if (!mw.config.get('wgPageName').match(/Wikipedia:Redirects_for_discussion/)) {
    	return;
    }

    // Initialize
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addButtons);
    } else {
        setTimeout(addButtons, 1000);
    }
    
    // Watch for dynamic content
    const observer = new MutationObserver(() => {
        addButtons();
    });
    
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();
// </nowiki>