Jump to content

User:Polygnotus/Scripts/Pageviews.js

From Wikipedia, the free encyclopedia
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>
// Wikipedia Pageviews Checker for common.js
// Checks pageview statistics for a list of articles and displays them sorted by views

async function checkPageviews() {
    showInputDialog();
}

function showInputDialog() {
    const fieldset = new OO.ui.FieldsetLayout({
        label: 'Enter article names to check pageviews'
    });

    const helpText = new OO.ui.LabelWidget({
        label: 'Supported formats:\n• [[Article Name]]\n• Article Name (one per line)'
    });

    const textInput = new OO.ui.MultilineTextInputWidget({
        rows: 10,
        placeholder: 'Paste your article names here...',
        classes: ['pageviews-input']
    });

    const timeRangeDropdown = new OO.ui.DropdownWidget({
        label: 'Time range',
        menu: {
            items: [
                new OO.ui.MenuOptionWidget({ data: 30, label: 'Last 30 days' }),
                new OO.ui.MenuOptionWidget({ data: 60, label: 'Last 60 days' }),
                new OO.ui.MenuOptionWidget({ data: 90, label: 'Last 90 days' })
            ]
        }
    });
    timeRangeDropdown.getMenu().selectItemByData(30);

    const statusArea = new OO.ui.MultilineTextInputWidget({
        rows: 8,
        readOnly: true,
        classes: ['pageviews-status']
    });
    statusArea.$element.hide();

    fieldset.addItems([
        new OO.ui.FieldLayout(helpText, { align: 'top' }),
        new OO.ui.FieldLayout(textInput, { label: 'Articles', align: 'top' }),
        new OO.ui.FieldLayout(timeRangeDropdown, { label: 'Time range', align: 'left' }),
        new OO.ui.FieldLayout(statusArea, { label: 'Status', align: 'top' })
    ]);

    const windowManager = new OO.ui.WindowManager();
    $('body').append(windowManager.$element);

    function MessageDialog(config) {
        MessageDialog.super.call(this, config);
    }
    OO.inheritClass(MessageDialog, OO.ui.ProcessDialog);

    MessageDialog.static.name = 'pageviewsDialog';
    MessageDialog.static.title = 'Article Pageviews Checker';
    MessageDialog.static.actions = [
        { action: 'check', label: 'Check Pageviews', flags: ['primary', 'progressive'] },
        { action: 'clear', label: 'Clear', flags: 'safe' },
        { action: 'cancel', label: 'Close', flags: 'safe' }
    ];

    MessageDialog.prototype.initialize = function() {
        MessageDialog.super.prototype.initialize.apply(this, arguments);
        this.content = new OO.ui.PanelLayout({ padded: true, expanded: false });
        this.content.$element.append(fieldset.$element);
        this.$body.append(this.content.$element);
    };

    MessageDialog.prototype.getActionProcess = function(action) {
        const dialog = this;
        if (action === 'check') {
            return new OO.ui.Process(function() {
                const input = textInput.getValue().trim();
                if (!input) {
                    OO.ui.alert('Please enter article names to check.');
                    return;
                }
                const days = timeRangeDropdown.getMenu().findSelectedItem().getData();
                processArticles(input, days, statusArea, dialog);
            });
        } else if (action === 'clear') {
            return new OO.ui.Process(function() {
                textInput.setValue('');
                statusArea.setValue('');
                statusArea.$element.hide();
            });
        }
        return MessageDialog.super.prototype.getActionProcess.call(this, action);
    };

    MessageDialog.prototype.getBodyHeight = function() {
        return 500;
    };

    windowManager.addWindows([new MessageDialog({ size: 'large' })]);
    windowManager.openWindow('pageviewsDialog');
}

function parseArticles(input) {
    const lines = input.split('\n');
    const articles = [];
    
    const patterns = [
        /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/,  // [[Article Name]] or [[Article Name|Display]]
        /^(.+)$/                             // Plain article name
    ];
    
    for (const line of lines) {
        const trimmedLine = line.trim();
        if (!trimmedLine) continue;
        
        for (const pattern of patterns) {
            const match = trimmedLine.match(pattern);
            if (match) {
                const articleName = match[1].trim();
                articles.push({
                    name: articleName,
                    original: trimmedLine
                });
                break;
            }
        }
    }
    
    return articles;
}

function deduplicateArticles(articles) {
    const seen = new Set();
    const uniqueArticles = [];
    
    for (const article of articles) {
        const normalizedName = article.name.toLowerCase().replace(/_/g, ' ');
        
        if (!seen.has(normalizedName)) {
            seen.add(normalizedName);
            uniqueArticles.push(article);
        }
    }
    
    return uniqueArticles;
}

async function processArticles(input, days, statusArea, dialog) {
    const allArticles = parseArticles(input);
    const articles = deduplicateArticles(allArticles);
    
    if (articles.length === 0) {
        OO.ui.alert('No valid article names found in the input.');
        return;
    }

    statusArea.$element.show();
    let statusText = '';
    
    const duplicateCount = allArticles.length - articles.length;
    if (duplicateCount > 0) {
        statusText = `Found ${allArticles.length} articles, removed ${duplicateCount} duplicates.\n`;
    }
    statusText += `Checking pageviews for ${articles.length} articles (last ${days} days)...\n`;
    statusArea.setValue(statusText);
    
    const results = [];
    const checkButton = dialog.actions.get({ actions: 'check' })[0];
    checkButton.setDisabled(true);
    checkButton.setLabel('Checking...');
    
    const endDate = new Date();
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - days);
    
    for (let i = 0; i < articles.length; i++) {
        const article = articles[i];
        const progress = `[${i + 1}/${articles.length}]`;
        
        statusText += `${progress} Checking ${article.name}...\n`;
        statusArea.setValue(statusText);
        
        try {
            const pageviews = await getPageviews(article.name, startDate, endDate);
            results.push({
                name: article.name,
                original: article.original,
                views: pageviews
            });
            
            statusText += `${progress}${article.name}: ${pageviews.toLocaleString()} views\n`;
            statusArea.setValue(statusText);
            
        } catch (error) {
            console.error(`Failed to check ${article.name}:`, error);
            results.push({
                name: article.name,
                original: article.original,
                views: 0
            });
            
            statusText += `${progress} ? ${article.name}: Failed to fetch data\n`;
            statusArea.setValue(statusText);
        }
        
        if (i < articles.length - 1) {
            await sleep(500);
        }
    }
    
    checkButton.setDisabled(false);
    checkButton.setLabel('Check Pageviews');
    statusText += `\n✓ Completed! Checked ${articles.length} articles.\n`;
    statusArea.setValue(statusText);
    
    results.sort((a, b) => b.views - a.views);
    
    displayResults(results, days);
}

async function getPageviews(articleName, startDate, endDate) {
    const project = mw.config.get('wgServerName');
    const encodedArticle = encodeURIComponent(articleName.replace(/ /g, '_'));
    
    const startStr = formatDate(startDate);
    const endStr = formatDate(endDate);
    
    const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/${project}/all-access/user/${encodedArticle}/daily/${startStr}/${endStr}`;
    
    const response = await fetch(url);
    
    if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    const data = await response.json();
    
    if (!data.items || data.items.length === 0) {
        return 0;
    }
    
    const totalViews = data.items.reduce((sum, item) => sum + (item.views || 0), 0);
    return totalViews;
}

function formatDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}${month}${day}`;
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function displayResults(results, days) {
    const windowManager = new OO.ui.WindowManager();
    $('body').append(windowManager.$element);

    function ResultsDialog(config) {
        ResultsDialog.super.call(this, config);
    }
    OO.inheritClass(ResultsDialog, OO.ui.ProcessDialog);

    ResultsDialog.static.name = 'resultsDialog';
    ResultsDialog.static.title = `Pageviews Results (Last ${days} days)`;
    ResultsDialog.static.actions = [
        { action: 'copyTable', label: 'Copy Table', flags: ['primary', 'progressive'] },
        { action: 'copyArticles', label: 'Copy Article Names', flags: 'progressive' },
        { action: 'cancel', label: 'Close', flags: 'safe' }
    ];

    ResultsDialog.prototype.initialize = function() {
        ResultsDialog.super.prototype.initialize.apply(this, arguments);
        this.content = new OO.ui.PanelLayout({ padded: true, expanded: false, scrollable: true });
        
        const tableHtml = '<table class="wikitable sortable" style="width: 100%;">' +
            '<thead><tr><th>Article Name</th><th>Pageviews</th></tr></thead>' +
            '<tbody>' +
            results.map(r => `<tr><td>${mw.html.escape(r.name)}</td><td style="text-align: right;">${r.views.toLocaleString()}</td></tr>`).join('') +
            '</tbody></table>';
        
        this.content.$element.append(tableHtml);
        this.$body.append(this.content.$element);
    };

    ResultsDialog.prototype.getActionProcess = function(action) {
        if (action === 'copyTable') {
            return new OO.ui.Process(function() {
                const tableText = 'Article Name\tPageviews\n' +
                    results.map(r => `${r.name}\t${r.views}`).join('\n');
                
                navigator.clipboard.writeText(tableText).then(() => {
                    mw.notify('Table copied to clipboard!', { type: 'success' });
                }).catch(() => {
                    mw.notify('Failed to copy to clipboard', { type: 'error' });
                });
            });
        } else if (action === 'copyArticles') {
            return new OO.ui.Process(function() {
                const articleNames = results.map(r => r.name).join('\n');
                
                navigator.clipboard.writeText(articleNames).then(() => {
                    mw.notify('Article names copied to clipboard!', { type: 'success' });
                }).catch(() => {
                    mw.notify('Failed to copy to clipboard', { type: 'error' });
                });
            });
        }
        return ResultsDialog.super.prototype.getActionProcess.call(this, action);
    };

    ResultsDialog.prototype.getBodyHeight = function() {
        return 500;
    };

    windowManager.addWindows([new ResultsDialog({ size: 'large' })]);
    windowManager.openWindow('resultsDialog');
}

function addPageviewsButton() {
    if (mw.config.get('wgNamespaceNumber') === -1) return;
    
    const portletId = mw.config.get('skin') === 'vector' ? 'p-cactions' : 'p-tb';
    mw.util.addPortletLink(
        portletId,
        '#',
        'Check Article Pageviews',
        't-check-pageviews',
        'Check pageview statistics for articles'
    );
    
    $('#t-check-pageviews').on('click', function(e) {
        e.preventDefault();
        checkPageviews();
    });
}

$(document).ready(function() {
    mw.loader.using(['oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets'], function() {
        addPageviewsButton();
    });
});

window.checkPageviews = checkPageviews;
// </nowiki>