User:DreamRimmer/adminnewslettertools.js

This is an old revision of this page, as edited by DreamRimmer (talk | contribs) at 11:55, 13 August 2025 (+). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.
/**
 * ANewsTools
 * Administrators' newsletter related updates for a specific month
 * @author DreamRimmer ([[en:User:DreanRimmer]])
 * 
 * <nowiki>
 */
$(document).ready(function() {
    function initializeANewsTools() {
        $('#mw-content-text > p').remove();
        $('#firstHeading').text('ANewsTools');

        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
            }),
            sectionSelect = new OO.ui.DropdownWidget({
                menu: {
                    items: [
                        new OO.ui.MenuOptionWidget({ data: 'all', label: 'All sections' }),
                        new OO.ui.MenuOptionWidget({ data: 'rights', label: 'Rights changes' }),
                        new OO.ui.MenuOptionWidget({ data: 'tech', label: 'Tech News' }),
                        new OO.ui.MenuOptionWidget({ data: 'centralized', label: 'CENT' }),
                        new OO.ui.MenuOptionWidget({ data: 'arbcom', label: 'ArbCom updates' }),
                        new OO.ui.MenuOptionWidget({ data: 'rfc', label: 'Recent RfCs' }),
                        new OO.ui.MenuOptionWidget({ data: 'misc', label: 'Miscellaneous' })
                    ]
                }
            }),
            checkButton = new OO.ui.ButtonWidget({
                label: 'Check',
                flags: ['primary', 'progressive']
            }),
            resultsContainer = $("<div>").hide();

        var currentDate = new Date();
        var currentMonth = String(currentDate.getMonth() + 1);
        var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
        var quickLinks = $('<p>').css({
            'display': 'flex',
            'justify-content': 'space-between',
            'align-items': 'center',
            'margin': '10px 0',
            'padding': '10px 15px',
            'background': '#f8f9fa',
            'border': '1px solid #a2a9b1',
            'border-radius': '3px'
        }).html(`
            <a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/${currentDate.getFullYear()}/${currentDate.getMonth() + 1}" target="_blank">Help with ${monthNames[currentDate.getMonth()]} ${currentDate.getFullYear()} issue</a>
            <span>&bull;</span>
            <a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/Archive" target="_blank">Archive</a>
            <span>&bull;</span>
            <a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/Subscribe" target="_blank">Subscribe</a>
            <span>&bull;</span>
            <a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/Write" target="_blank">Write</a>
            <span>&bull;</span>
            <a href="https://en.wikipedia.org/wiki/Wikipedia_talk:Administrators%27_newsletter" target="_blank">Discuss</a>
        `);
        
        monthSelect.getMenu().selectItemByData(currentMonth);
        sectionSelect.getMenu().selectItemByData('all');

        var labels = {
            monthLabel: $('<p>').text('Month:').css('font-weight', 'bold'),
            yearLabel: $('<p>').text('Year:').css('font-weight', 'bold'),
            sectionLabel: $('<p>').text('Section:').css('font-weight', 'bold'),
            description: $('<p>').html('The <a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter" target="_blank">administrators\' newsletter</a> is a monthly update containing relevant information on administrative issues aimed at <a href="https://en.wikipedia.org/wiki/WP:ADMIN" target="_blank">administrators</a>. It is intended to help keep administrators up to date with changes to Wikipedia that they may otherwise miss. You can view various Administrators\' newsletter related updates for a specific month here. Please choose a month and year to get results.')
        };

        $('#mw-content-text').append(
            labels.description,
            quickLinks,
            $('<div>').css({
                'background': '#f8f9fa',
                'border': '1px solid #a2a9b1',
                'padding': '15px',
                'margin-bottom': '20px',
                'border-radius': '3px'
            }).append(
                $('<h3>').text('Select Month, Year and Section'),
                $('<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(labels.sectionLabel, sectionSelect.$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 parseWikilinks(text, isMeta = false) {
            if (!text) return '';
            
            const baseWikiUrl = isMeta ? 'https://meta.wikimedia.org' : 'https://en.wikipedia.org';
            
            try {
                text = text.replace(/\[([^\s\]]+)\s+([^\]]+)\]/g, function(match, url, displayText) {
                    try {
                        if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
                            return `<a href="${url}" target="_blank">${displayText}</a>`;
                        }
                        return match;
                    } catch (e) {
                        return match;
                    }
                });
                
                text = text.replace(/\[([^\s\]]+)\]/g, function(match, url) {
                    try {
                        if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
                            return `<a href="${url}" target="_blank">${url}</a>`;
                        }
                        return match;
                    } catch (e) {
                        return match;
                    }
                });
                
                text = text.replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g, function(match, link, pipe, display) {
                    try {
                        const displayText = display || link;
                        let url;
                        
                        if (link.startsWith('Special:')) {
                            if (link.startsWith('Special:Diff/')) {
                                const diffPart = link.replace('Special:Diff/', '');
                                const hashIndex = diffPart.indexOf('#');
                                const diffId = hashIndex !== -1 ? diffPart.substring(0, hashIndex) : diffPart;
                                const fragment = hashIndex !== -1 ? diffPart.substring(hashIndex) : '';
                                
                                if (/^\d+$/.test(diffId)) {
                                    url = `${baseWikiUrl}/w/index.php?diff=${diffId}${fragment}`;
                                } else {
                                    return match;
                                }
                            } else if (link.startsWith('Special:Contributions/')) {
                                const user = link.replace('Special:Contributions/', '');
                                if (user.trim()) {
                                    url = `${baseWikiUrl}/wiki/Special:Contributions/${encodeURIComponent(user)}`;
                                } else {
                                    return match;
                                }
                            } else {
                                url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
                            }
                        } else if (link.startsWith('User:') || link.startsWith('User talk:')) {
                            url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
                        } else if (link.startsWith('Wikipedia:')) {
                            url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
                        } else {
                            url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
                        }
                        
                        return `<a href="${url}" target="_blank">${displayText}</a>`;
                    } catch (e) {
                        return match;
                    }
                });
                
                return text;
            } catch (e) {
                return text;
            }
        }

        function getWeeksInMonth(year, month) {
            const weeks = [];
            const startDate = new Date(year, month - 1, 1);
            const endDate = new Date(year, month, 0);
            
            let currentDate = new Date(startDate);
            while (currentDate <= endDate) {
                const weekNumber = getISOWeek(currentDate);
                if (!weeks.includes(weekNumber)) {
                    weeks.push(weekNumber);
                }
                currentDate.setDate(currentDate.getDate() + 1);
            }
            return weeks;
        }

        function getISOWeek(date) {
            const target = new Date(date.valueOf());
            const dayNr = (date.getDay() + 6) % 7;
            target.setDate(target.getDate() - dayNr + 3);
            const jan4 = new Date(target.getFullYear(), 0, 4);
            const dayDiff = (target - jan4) / 86400000;
            return 1 + Math.ceil(dayDiff / 7);
        }

        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, entry.logid, entry.comment || '']);
                    }
                    if (oldGroups.has(grp) && !newGroups.has(grp)) {
                        removed.push([timestamp, userTarget, grp, actor, entry.logid, entry.comment || '']);
                    }
                });
            });

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

        function createRightsTable(title, data, isMeta = false) {
            if (!data.length) return '';

            let html = `<h4>${title}</h4><table class="wikitable sortable"><thead><tr><th>Timestamp</th><th>User</th><th>Right</th><th>By</th><th>Comment</th></tr></thead><tbody>`;
        
            data.forEach(([ts, tgt, grp, by, logid, comment]) => {
                const sign = title.includes('granted') ? '+' : '-';
                
                const baseUrl = isMeta ? 'https://meta.wikimedia.org' : 'https://en.wikipedia.org';
                const logUrl = `${baseUrl}/w/index.php?title=Special:Log&logid=${logid}`;
                const timestampLink = `<a href="${logUrl}" target="_blank">${formatDateFromTimestamp(ts)}</a>`;
                
                let userLink, byLink;
                if (isMeta) {
                    const cleanUsername = tgt.replace('@enwiki', '');
                    const finalUsername = cleanUsername.startsWith('User:') ? cleanUsername.substring(5) : cleanUsername;
                    userLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalUsername)}" target="_blank">${tgt}</a>`;
                    const finalByUsername = by.startsWith('User:') ? by.substring(5) : by;
                    byLink = `<a href="https://meta.wikimedia.org/wiki/User:${encodeURIComponent(finalByUsername)}" target="_blank">${by}</a>`;
                } else {
                    const finalTgtUsername = tgt.startsWith('User:') ? tgt.substring(5) : tgt;
                    userLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalTgtUsername)}" target="_blank">${tgt}</a>`;
                    const finalByUsername = by.startsWith('User:') ? by.substring(5) : by;
                    byLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalByUsername)}" target="_blank">${by}</a>`;
                }
                
                html += `<tr><td>${timestampLink}</td><td>${userLink}</td><td>${sign}${grp}</td><td>${byLink}</td><td>${parseWikilinks(comment, isMeta)}</td></tr>`;
            });

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

        function createEditTable(title, data, showDiff = false) {
            if (!data.length) return `<p>No ${title.toLowerCase()} found for this period.</p>`;

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

            data.forEach(item => {
                const timestamp = showDiff && item.revid ? 
                    `<a href="https://en.wikipedia.org/w/index.php?diff=${item.revid}" target="_blank">${formatDateFromTimestamp(item.timestamp)}</a>` :
                    formatDateFromTimestamp(item.timestamp);
                
                const finalUsername = item.user.startsWith('User:') ? item.user.substring(5) : item.user;
                const userLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalUsername)}" target="_blank">${item.user}</a>`;
                
                html += `<tr><td>${timestamp}</td><td>${userLink}</td><td>${parseWikilinks(item.comment || '', false)}</td></tr>`;
            });

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

        function createCollapsibleSection(title, content, extraLinks = '') {
            const sectionId = title.replace(/\s+/g, '').toLowerCase();
            return `
                <div style="border: 1px solid #ddd; margin-bottom: 15px; border-radius: 3px;">
                    <div style="background: #f8f9fa; padding: 10px; border-bottom: 1px solid #ddd; cursor: pointer;" onclick="toggleSection('${sectionId}')">
                        <h3 style="margin: 0; display: inline-block;">${title}</h3>
                        <span id="${sectionId}-toggle" style="float: right;">[hide]</span>
                        ${extraLinks}
                    </div>
                    <div id="${sectionId}" style="display: block; padding: 15px;">
                        ${content}
                    </div>
                </div>
            `;
        }

        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 fetchAllRevisions(api, params, results = []) {
            return api.get(params).then(response => {
                const pages = response.query.pages || {};
                Object.values(pages).forEach(page => {
                    if (page.revisions) {
                        results.push(...page.revisions);
                    }
                });
                if (response.continue) {
                    return fetchAllRevisions(api, { ...params, ...response.continue }, results);
                }
                return results;
            });
        }

        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.Api();

        function fetchRightsChanges(startTs, endTs, startLabel, endLabel) {

            return 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|ids',
                        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|ids',
                        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);

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

                return tablesHtml || '<p>No rights changes found for this period.</p>';
            });
        }

        function fetchTechNews(year, month) {
            const weeks = getWeeksInMonth(year, month);
            
            const promises = weeks.map(week => {
                return metaApi.get({
                    action: 'parse',
                    page: `Tech/News/${year}/${week}`,
                    format: 'json',
                    prop: 'text'
                }).then(response => {
                    if (response.parse && response.parse.text) {
                        const html = response.parse.text['*'];
                        const match = html.match(/(\d{4}), week (\d+) \(([^)]+)\)/);
                        if (match) {
                            const weekDate = match[3];
                            const parsedDate = new Date(weekDate);
                            if (parsedDate.getMonth() === month - 1) {
                                return { week, date: weekDate, url: `https://meta.wikimedia.org/wiki/Tech/News/${year}/${week}` };
                            }
                        }
                    }
                    return null;
                }).catch(() => null);
            });

            return Promise.all(promises).then(results => {
                const validWeeks = results.filter(r => r !== null);
                if (!validWeeks.length) {
                    return '<p>No tech news found for this period.</p>';
                }

                let html = '<h4>Tech News Links</h4><ul>';
                validWeeks.forEach(({ week, date, url }) => {
                    html += `<li><a href="${url}" target="_blank">Tech/News/${year}/${week}</a> (${date})</li>`;
                });
                html += '</ul>';
                return html;
            });
        }

        function fetchCentralizedDiscussion(startTs, endTs) {
            
            return fetchAllRevisions(localApi, {
                action: 'query',
                titles: 'Template:Centralized discussion',
                prop: 'revisions',
                rvstart: endTs,
                rvend: startTs,
                rvlimit: 'max',
                rvprop: 'timestamp|user|comment|ids',
                format: 'json'
            }).then(revisions => {
                return createEditTable('CENT edits', revisions, true);
            });
        }

        function fetchArbComNotices(startTs, endTs) {
            
            return fetchAllRevisions(localApi, {
                action: 'query',
                titles: 'Wikipedia:Arbitration Committee/Noticeboard',
                prop: 'revisions',
                rvstart: endTs,
                rvend: startTs,
                rvlimit: 'max',
                rvprop: 'timestamp|user|comment|tags|ids',
                format: 'json'
            }).then(revisions => {
                const filtered = revisions.filter(rev => rev.tags && rev.tags.includes('discussiontools-newtopic'));
                return createEditTable('ArbCom topics', filtered, true);
            });
        }

        function checkPageExists(title) {
            return localApi.get({
                action: 'query',
                titles: title,
                format: 'json'
            }).then(response => {
                const pages = response.query.pages || {};
                return !Object.values(pages).some(page => page.hasOwnProperty('missing'));
            }).catch(() => false);
        }

        function fetchData() {
            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());
            const section = sectionSelect.getMenu().findSelectedItem().getData();

            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 currentDate = new Date();
            const isCurrentOrLastMonth = (year === currentDate.getFullYear() && month >= currentDate.getMonth()) ||
                                        (year === currentDate.getFullYear() - 1 && month === 12 && currentDate.getMonth() === 0);

            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));

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

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

            const promises = [];
            const sections = [];

            if (section === 'all' || section === 'rights') {
                promises.push(fetchRightsChanges(startTs, endTs, startLabel, endLabel));
                sections.push('rights');
            }

            if (section === 'all' || section === 'tech') {
                promises.push(fetchTechNews(year, month));
                sections.push('tech');
            }

            if (section === 'all' || section === 'centralized') {
                promises.push(fetchCentralizedDiscussion(startTs, endTs));
                sections.push('centralized');
            }

            if (section === 'all' || section === 'arbcom') {
                promises.push(fetchArbComNotices(startTs, endTs));
                sections.push('arbcom');
            }

            Promise.all(promises).then(results => {
                resultsContainer.empty();

                const headerHtml = `<h2>Results from ${startLabel} to ${endLabel}</h2>`;
                resultsContainer.append(headerHtml);

                window.toggleSection = function(sectionId) {
                    const content = document.getElementById(sectionId);
                    const toggle = document.getElementById(sectionId + '-toggle');
                    if (content.style.display === 'none') {
                        content.style.display = 'block';
                        toggle.textContent = '[hide]';
                    } else {
                        content.style.display = 'none';
                        toggle.textContent = '[show]';
                    }
                };

                let resultIndex = 0;

                if (sections.includes('rights')) {
                    const rightsHtml = createCollapsibleSection('Rights changes', results[resultIndex]);
                    resultsContainer.append(rightsHtml);
                    resultIndex++;
                }

                if (sections.includes('tech')) {
                    const techHtml = createCollapsibleSection('Tech News', results[resultIndex]);
                    resultsContainer.append(techHtml);
                    resultIndex++;
                }

                if (sections.includes('centralized')) {
                    const centralizedHtml = createCollapsibleSection('CENT', results[resultIndex]);
                    resultsContainer.append(centralizedHtml);
                    resultIndex++;
                }

                if (sections.includes('arbcom')) {
                    const arbcomHtml = createCollapsibleSection('ArbCom updates', results[resultIndex]);
                    resultsContainer.append(arbcomHtml);
                    resultIndex++;
                }
                
                if ((section === 'all' || section === 'arbcom') && isCurrentOrLastMonth) {
                    const arbcomTasksHtml = createCollapsibleSection('ArbCom Open and Recently closed tasks', '<p><a href="https://en.wikipedia.org/wiki/Template:ArbComOpenTasks" target="_blank">ArbCom Open and Recently closed tasks</a></p>');
                    resultsContainer.append(arbcomTasksHtml);
                }

                if ((section === 'all' || section === 'rfc') && isCurrentOrLastMonth) {
                    const rfcHtml = createCollapsibleSection('Recent RfCs', '<p><a href="https://en.wikipedia.org/wiki/Wikipedia:Requests_for_comment/All" target="_blank">Recent RfCs</a></p>');
                    resultsContainer.append(rfcHtml);
                }
                if (section === 'all' || section === 'misc') {
                    const miscPromises = [];
                    let miscSections = [];
                    
                    if (year >= 2025 && isCurrentOrLastMonth) {
                        miscSections.push('<h4>Backlog drive schedule</h4><p><a href="https://en.wikipedia.org/wiki/Wikipedia:Backlog_drive_schedule" target="_blank">Wikipedia:Backlog drive schedule</a></p>');
                    }
                    
                    if ((year > 2024 || (year === 2024 && month >= 10)) && isCurrentOrLastMonth) {
                        miscSections.push('<h4>Administrator elections</h4><p><a href="https://en.wikipedia.org/wiki/Wikipedia:Administrator_elections" target="_blank">Administrator elections</a> updates</p>');
                    }
                    
                    if (year >= 2006) {
                        miscPromises.push(
                            checkPageExists(`Wikipedia:Arbitration_Committee_Elections_December_${year}`)
                                .then(exists => exists ? `<h4>Arbitration Committee elections</h4><p><a href="https://en.wikipedia.org/wiki/Wikipedia:Arbitration_Committee_Elections_December_${year}" target="_blank">Arbitration Committee elections</a></p>` : '')
                        );
                    } else {
                        miscPromises.push(Promise.resolve(''));
                    }
                    
                    if (year >= 2024 && isCurrentOrLastMonth) {
                        miscSections.push('<h4>UCoC election updates</h4><p><a href="https://meta.wikimedia.org/wiki/Universal_Code_of_Conduct/Coordinating_Committee/Election" target="_blank">UCoC election updates</a></p>');
                    }
                    
                    if (miscPromises.length > 0) {
                        Promise.all(miscPromises).then(dynamicSections => {
                            let miscContent = miscSections.join('') + dynamicSections.join('');
                            
                            if (miscContent) {
                                const miscHtml = createCollapsibleSection('Miscellaneous', miscContent);
                                resultsContainer.append(miscHtml);
                            }
                        });
                    } else {
                        let miscContent = miscSections.join('');
                        
                        if (miscContent) {
                            const miscHtml = createCollapsibleSection('Miscellaneous', miscContent);
                            resultsContainer.append(miscHtml);
                        }
                    }
                }

            }).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', fetchData);
    }

    $.when(mw.loader.using('mediawiki.util'), $.ready).then(function() {
        mw.util.addPortletLink(
            'p-tb',
            mw.util.getUrl('Special:BlankPage/ANewsTools'),
            'ANews Tools',
            't-anewstools',
            'View various Administrators\' newsletter related updates for a specific month'
        );
    });

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