User:DreamRimmer/adminnewslettertools.js

This is an old revision of this page, as edited by DreamRimmer (talk | contribs) at 18:18, 7 August 2025 (+). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)
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>
$(document).ready(function() {
    function initializeRightsChanges() {
        $('#mw-content-text > p').remove();
        $('#firstHeading').text('RightsChanges');

        var monthSelect = new OO.ui.DropdownWidget({
                menu: {
                    items: [
                        new OO.ui.MenuOptionWidget({ data: '1', label: 'January' }),
                        new OO.ui.MenuOptionWidget({ data: '2', label: 'February' }),
                        new OO.ui.MenuOptionWidget({ data: '3', label: 'March' }),
                        new OO.ui.MenuOptionWidget({ data: '4', label: 'April' }),
                        new OO.ui.MenuOptionWidget({ data: '5', label: 'May' }),
                        new OO.ui.MenuOptionWidget({ data: '6', label: 'June' }),
                        new OO.ui.MenuOptionWidget({ data: '7', label: 'July' }),
                        new OO.ui.MenuOptionWidget({ data: '8', label: 'August' }),
                        new OO.ui.MenuOptionWidget({ data: '9', label: 'September' }),
                        new OO.ui.MenuOptionWidget({ data: '10', label: 'October' }),
                        new OO.ui.MenuOptionWidget({ data: '11', label: 'November' }),
                        new OO.ui.MenuOptionWidget({ data: '12', label: 'December' })
                    ]
                }
            }),
            yearInput = new OO.ui.NumberInputWidget({
                value: new Date().getFullYear(),
                min: 2005,
                max: new Date().getFullYear() + 1,
                step: 1
            }),
            checkButton = new OO.ui.ButtonWidget({
                label: 'Check',
                flags: ['primary', 'progressive']
            }),
            resultsContainer = $("<div>").hide();

        var currentDate = new Date();
        var currentMonth = String(currentDate.getMonth() + 1);
        monthSelect.getMenu().selectItemByData(currentMonth);

        var labels = {
            monthLabel: $('<p>').text('Month:').css('font-weight', 'bold'),
            yearLabel: $('<p>').text('Year:').css('font-weight', 'bold'),
            description: $('<p>').text('View user rights changes for English Wikipedia for a specific month.')
        };

        $('#mw-content-text').append(
            labels.description,
            '<br/>',
            $('<div>').css({
                'background': '#f8f9fa',
                'border': '1px solid #a2a9b1',
                'padding': '15px',
                'margin-bottom': '20px',
                'border-radius': '3px'
            }).append(
                $('<h3>').text('Select Month and Year'),
                $('<div>').css({
                    'display': 'flex',
                    'gap': '15px',
                    'align-items': 'end',
                    'flex-wrap': 'wrap'
                }).append(
                    $('<div>').append(labels.monthLabel, monthSelect.$element),
                    $('<div>').append(labels.yearLabel, yearInput.$element),
                    $('<div>').append(checkButton.$element)
                )
            ),
            resultsContainer
        );

        function formatDate(date) {
            const options = { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'UTC' };
            return date.toLocaleDateString('en-GB', options).replace(',', '') + ' UTC';
        }

        function formatDateFromTimestamp(timestamp) {
            const date = new Date(timestamp);
            const options = { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'UTC' };
            return date.toLocaleDateString('en-GB', options).replace(',', '') + ' UTC';
        }

        function processLogEntries(entries, targetGroups) {
            const granted = [];
            const removed = [];

            entries.forEach(entry => {
                if (!entry.title) return;

                const timestamp = entry.timestamp;
                const userTarget = entry.title;
                const actor = entry.user;

                let oldGroups = new Set();
                let newGroups = new Set();

                if (entry.params) {
                    if (entry.params.oldgroups) {
                        oldGroups = new Set(entry.params.oldgroups);
                    }
                    if (entry.params.newgroups) {
                        newGroups = new Set(entry.params.newgroups);
                    }
                    if (entry.params['4::oldgroups']) {
                        oldGroups = new Set(entry.params['4::oldgroups']);
                    }
                    if (entry.params['5::newgroups']) {
                        newGroups = new Set(entry.params['5::newgroups']);
                    }
                }

                if (entry.comment) {
                    const commentMatch = entry.comment.match(/changed group membership for (.+?) from (.+?) to (.+)/);
                    if (commentMatch) {
                        const oldGroupsStr = commentMatch[2];
                        const newGroupsStr = commentMatch[3];

                        if (oldGroupsStr !== '(none)') {
                            oldGroups = new Set(oldGroupsStr.split(', '));
                        }
                        if (newGroupsStr !== '(none)') {
                            newGroups = new Set(newGroupsStr.split(', '));
                        }
                    }
                }

                targetGroups.forEach(grp => {
                    if (newGroups.has(grp) && !oldGroups.has(grp)) {
                        granted.push([timestamp, userTarget, grp, actor]);
                    }
                    if (oldGroups.has(grp) && !newGroups.has(grp)) {
                        removed.push([timestamp, userTarget, grp, actor]);
                    }
                });
            });

            return { granted: granted.sort(), removed: removed.sort() };
        }

        function createTable(title, data) {
            if (!data.length) return '';

            let html = `<h3>${title}</h3><table class="wikitable sortable"><thead><tr><th>Date</th><th>User</th><th>Right</th><th>By</th></tr></thead><tbody>`;

            data.forEach(([ts, tgt, grp, by]) => {
                const sign = title.includes('granted') ? '+' : '-';
                html += `<tr><td>${formatDateFromTimestamp(ts)}</td><td>${tgt}</td><td>${sign}${grp}</td><td>${by}</td></tr>`;
            });

            html += '</tbody></table>';
            return html;
        }

        function fetchAllLogEvents(api, params, results = []) {
            return api.get(params).then(response => {
                results.push(...(response.query.logevents || []));
                if (response.continue) {
                    return fetchAllLogEvents(api, { ...params, ...response.continue }, results);
                }
                return results;
            });
        }

        function fetchRightsChanges() {
            var selectedItem = monthSelect.getMenu().findSelectedItem();
            if (!selectedItem) {
                OO.ui.alert('Please select a month');
                return;
            }

            const month = parseInt(selectedItem.getData());
            const year = parseInt(yearInput.getValue());

            if (year < 2005 || year > new Date().getFullYear() + 1) {
                OO.ui.alert('Please enter a valid year between 2005 and ' + (new Date().getFullYear() + 1));
                return;
            }

            const startDt = new Date(Date.UTC(year, month - 1, 1, 0, 0, 0));
            const nextMonthDt = new Date(Date.UTC(month === 12 ? year + 1 : year, month % 12, 1, 0, 0, 0));

            const startTs = startDt.toISOString();
            const endTs = nextMonthDt.toISOString();

            const startLabel = formatDate(startDt);
            const endLabel = formatDate(new Date(nextMonthDt.getTime() - 1000));

            const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { headers: { 'Api-User-Agent': 'https://en.wikipedia.org/wiki/User:DreamRimmer' } });
            const localApi = new mw.ForeignApi('https://en.wikipedia.org/w/api.php', { headers: { 'Api-User-Agent': 'https://en.wikipedia.org/wiki/User:DreamRimmer' } });

            resultsContainer.empty();
            resultsContainer.append($('<p>').text('Loading...'));
            resultsContainer.show();

            checkButton.setDisabled(true);
            checkButton.setLabel('Loading...');

            Promise.all([
                fetchAllLogEvents(metaApi, {
                    action: 'query',
                    list: 'logevents',
                    letype: 'rights',
                    lestart: endTs,
                    leend: startTs,
                    lelimit: 'max',
                    leprop: 'timestamp|title|user|userid|details|params|type|comment',
                    format: 'json'
                }),
                fetchAllLogEvents(localApi, {
                    action: 'query',
                    list: 'logevents',
                    letype: 'rights',
                    lestart: endTs,
                    leend: startTs,
                    lelimit: 'max',
                    leprop: 'timestamp|title|user|userid|details|params|type|comment',
                    format: 'json'
                })
            ]).then(([metaEntries, localEntries]) => {
                const metaFiltered = metaEntries.filter(entry =>
                    entry.title && entry.title.endsWith('@enwiki')
                );

                const targetMeta = ['suppress', 'checkuser'];
                const targetLocal = ['sysop', 'interface-admin', 'bureaucrat'];

                const metaResults = processLogEntries(metaFiltered, targetMeta);
                const localResults = processLogEntries(localEntries, targetLocal);

                resultsContainer.empty();

                $('<h2>').text(`Rights changes from ${startLabel} to ${endLabel}`).appendTo(resultsContainer);

                const tablesHtml = createTable('Meta granted', metaResults.granted) +
                                   createTable('Meta removed', metaResults.removed) +
                                   createTable('Local granted', localResults.granted) +
                                   createTable('Local removed', localResults.removed);

                if (tablesHtml) {
                    resultsContainer.append(tablesHtml);
                } else {
                    resultsContainer.append($('<p>').text('No rights changes found for this period.'));
                }
            }).catch(error => {
                console.error('Error:', error);
                resultsContainer.empty();
                resultsContainer.append($('<p>').css('color', 'red').text(`error fetching data: ${error}`));
            }).finally(() => {
                checkButton.setDisabled(false);
                checkButton.setLabel('Check');
            });
        }

        checkButton.on('click', fetchRightsChanges);
    }

    $.when(mw.loader.using('mediawiki.util'), $.ready).then(function() {
        mw.util.addPortletLink(
            'p-tb',
            mw.util.getUrl('Special:BlankPage/RightsChanges'),
            'Rights Changes',
            't-rightschanges',
            'View rights changes for a specific month'
        );
    });

    if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' && mw.config.get('wgTitle').split('/', 2)[1] === 'RightsChanges') {
        $.when(
            mw.loader.using([
                'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows'
            ]), 
            $.ready
        ).then(function() {
            initializeRightsChanges();
        });
    }
});
//</nowiki>