Accessible Telegram Web

Improves Telegram Web accessibility for screen reader users with UI fixes and feature enhancements.

// ==UserScript==
// @name         Accessible Telegram Web
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Improves Telegram Web accessibility for screen reader users with UI fixes and feature enhancements.
// @author       mahmood hozhabri
// @match        https://web.telegram.org/a/*
// @grant        GM_info
// @run-at       document-end
// ==/UserScript==
(function() {
'use strict';

// Simulates a universal click by dispatching multiple event types to ensure reliability.
function simulateUniversalClick(element) {
    if (!element) return;
    const isTouchDevice = 'ontouchstart' in window;

    if (isTouchDevice) {
        element.dispatchEvent(new TouchEvent('touchstart', { bubbles: true, cancelable: true, view: window }));
        element.dispatchEvent(new TouchEvent('touchend', { bubbles: true, cancelable: true, view: window }));
    }

    element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }));
    element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window }));
    element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
}

// =========================================================================
// SECTION: Unified Live Region for Announcements
// ... (All other functions from before remain the same)
// ...
// =========================================================================
let unifiedLiveRegion;
let currentChatStatusText = '';
let lastAnnouncedMessageId = null;
let announcementTimeout = null;
let isInitialPageLoad = true;

// Initializes the visually hidden live region element.
function createUnifiedLiveRegion() {
    if (unifiedLiveRegion) return;
    unifiedLiveRegion = document.createElement('div');
    unifiedLiveRegion.setAttribute('aria-live', 'polite');
    unifiedLiveRegion.setAttribute('aria-atomic', 'true');
    unifiedLiveRegion.style.cssText = 'position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; overflow: hidden;';
    document.body.appendChild(unifiedLiveRegion);
}

// Sends text to the live region to be announced by a screen reader.
function announceText(text, isMessage = false) {
    if (!unifiedLiveRegion) return;

    if (announcementTimeout) {
        clearTimeout(announcementTimeout);
        announcementTimeout = null;
    }

    if (!text.trim()) {
        if (unifiedLiveRegion.textContent.trim() !== '' && !unifiedLiveRegion.dataset.isAnnouncingMessage) {
             unifiedLiveRegion.textContent = '';
        }
        return;
    }

    if (isMessage) {
        unifiedLiveRegion.dataset.isAnnouncingMessage = 'true';
        unifiedLiveRegion.textContent = text;
        announcementTimeout = setTimeout(() => {
            if (unifiedLiveRegion.textContent === text) {
                unifiedLiveRegion.textContent = currentChatStatusText ? `User status: ${currentChatStatusText}` : '';
                delete unifiedLiveRegion.dataset.isAnnouncingMessage;
            }
        }, 3000);
    } else {
        delete unifiedLiveRegion.dataset.isAnnouncingMessage;
        if (unifiedLiveRegion.textContent !== `User status: ${text}`) {
            unifiedLiveRegion.textContent = `User status: ${text}`;
        }
    }
}

let statusObserverInstance = null;
let currentObservedChatInfoContainer = null;
let statusUpdateDebounceTimeout = null;

// Monitors the chat header for status changes (e.g., "online", "typing") and announces them.
function monitorChatStatus() {
    const chatInfoContainer = document.querySelector('.ChatInfo .info');

    if (!chatInfoContainer) {
        if (statusObserverInstance) {
            statusObserverInstance.disconnect();
            statusObserverInstance = null;
            currentObservedChatInfoContainer = null;
        }
        currentChatStatusText = '';
        announceText('');
        return;
    }

    if (chatInfoContainer === currentObservedChatInfoContainer) {
        return;
    }

    if (statusObserverInstance) {
        statusObserverInstance.disconnect();
    }

    const extractStatusText = (element) => {
        const commonExclusions = ['last seen', 'ago', 'connecting', 'updating', 'synchronizing', 'subscriber', 'member', 'bot', 'monthly users'];

        const checkAndFilter = (text) => {
            if (!text) return '';
            const lowerText = text.toLowerCase();
            for (const exclusion of commonExclusions) {
                if (lowerText.includes(exclusion)) {
                    return '';
                }
            }
            return text;
        };

        const typingStatusElem = element.querySelector('p.typing-status');
        if (typingStatusElem && typingStatusElem.textContent.trim()) {
            const text = typingStatusElem.textContent.trim().replace(/\.{3}$/, '');
            return checkAndFilter(text);
        }

        const userStatusSpan = element.querySelector('.status .user-status');
        if (userStatusSpan && userStatusSpan.textContent.trim()) {
            const text = userStatusSpan.textContent.trim();
            return checkAndFilter(text);
        }

        const generalStatusElem = element.querySelector('.status');
        if (generalStatusElem && generalStatusElem.textContent.trim()) {
            const text = generalStatusElem.textContent.trim();
            const fullNameElem = element.querySelector('.fullName');
            const fullNameText = fullNameElem ? fullNameElem.textContent.trim() : '';
            if (text !== fullNameText) {
                return checkAndFilter(text);
            }
        }
        return '';
    };

    const updateStatusAnnouncement = () => {
        const newStatusText = extractStatusText(chatInfoContainer);
        if (newStatusText !== currentChatStatusText) {
            currentChatStatusText = newStatusText;
            if (currentChatStatusText.trim()) {
                announceText(currentChatStatusText);
            } else {
                announceText('');
            }
        }
    };

    updateStatusAnnouncement();

    statusObserverInstance = new MutationObserver((mutations) => {
        const relevantChange = mutations.some(mutation =>
            mutation.target === chatInfoContainer ||
            (mutation.target.parentElement === chatInfoContainer && (
                mutation.target.matches('p.typing-status') || mutation.target.matches('.status')
            )) ||
            (mutation.type === 'characterData' && mutation.target.parentElement && (
                mutation.target.parentElement.matches('p.typing-status') ||
                mutation.target.parentElement.matches('.status') ||
                mutation.target.parentElement.closest('p.typing-status') ||
                mutation.target.parentElement.closest('.status .user-status')
            ))
        );

        if (relevantChange) {
            if (statusUpdateDebounceTimeout) clearTimeout(statusUpdateDebounceTimeout);
            statusUpdateDebounceTimeout = setTimeout(updateStatusAnnouncement, 50);
        }
    });

    currentObservedChatInfoContainer = chatInfoContainer;
    const observerOptions = { childList: true, subtree: true, characterData: true };
    statusObserverInstance.observe(chatInfoContainer, observerOptions);
}

// =========================================================================
// SECTION: Chat List Processing
// ...
// (The function processChatList is unchanged)
function processChatList() {
    const chats = document.querySelectorAll('#LeftColumn .ListItem.Chat');
    chats.forEach(chat => {
        const link = chat.querySelector('a.ListItem-button');
        if (!link) return;

        const nameElem = chat.querySelector('.fullName'),
              timeElem = chat.querySelector('.time'),
              messageElem = chat.querySelector('.last-message-summary'),
              senderElem = chat.querySelector('.sender-name'),
              unreadBadge = chat.querySelector('.ChatBadge.unread .tgKbsVmz');

        if (!nameElem || !timeElem || !messageElem ||
            !nameElem.textContent.trim() ||
            !timeElem.textContent.trim() ||
            !messageElem.textContent.trim()) {
            return;
        }

        const name = nameElem.textContent.trim();
        const time = timeElem.textContent.trim();
        const message = messageElem.textContent.trim();
        const sender = senderElem ? senderElem.textContent.trim() : '';
        const unreadCount = unreadBadge ? unreadBadge.textContent.trim() : '';
        let chatTypePrefix = '';

        if (chat.classList.contains('private')) {
            chatTypePrefix = `${name}. `;
        } else if (chat.classList.contains('forum') || senderElem) {
            chatTypePrefix = `Group: ${name}. `;
        } else {
            chatTypePrefix = `Channel: ${name}. `;
        }

        let ariaLabel = chatTypePrefix;
        if (unreadCount) ariaLabel += `${unreadCount} unread messages. `;
        ariaLabel += sender ? `from ${sender}: ${message}. ` : `${message}. `;
        ariaLabel += `${time}.`;

        const lastState = chat.getAttribute('data-last-state-signature');
        if (lastState !== ariaLabel) {
            link.setAttribute('aria-label', ariaLabel);
            chat.setAttribute('data-last-state-signature', ariaLabel);
        }
    });
}
// =========================================================================
// SECTION: Custom Accessible Menus
// ...
// (The functions createAccessibleMenu and showAccessibleContextMenu are unchanged)
function createAccessibleMenu(title, items) {
    const existingMenu = document.getElementById('accessible-context-menu');
    if (existingMenu) existingMenu.remove();

    const overlay = document.createElement('div');
    overlay.id = 'accessible-context-menu';
    overlay.style.cssText = `
        position: fixed; top: 0; left: 0; width: 100%; height: 100%;
        background-color: rgba(0, 0, 0, 0.5); z-index: 9999;
        display: flex; justify-content: center; align-items: center;
    `;

    const menu = document.createElement('div');
    menu.setAttribute('role', 'dialog');
    menu.setAttribute('aria-modal', 'true');
    menu.setAttribute('aria-label', title);
    menu.style.cssText = `
        background-color: white; color: black; border-radius: 12px;
        padding: 10px; max-width: 80%; box-shadow: 0 4px 12px rgba(0,0,0,0.2);
    `;

    const menuList = document.createElement('ul');
    menuList.style.cssText = 'list-style: none; padding: 0; margin: 0;';

    items.forEach(item => {
        const listItem = document.createElement('li');
        const button = document.createElement('button');
        button.textContent = item.text;
        button.style.cssText = `
            width: 100%; padding: 12px 16px; border: none; background: none;
            text-align: left; font-size: 17px; cursor: pointer;
            color: ${item.isDestructive ? '#ff3b30' : '#007aff'};
            ${item.disabled ? 'color: #8e8e93; cursor: not-allowed;' : ''}
        `;
        if (!item.disabled) {
            button.onclick = () => {
                item.action();
                if (!item.keepMenuOpen) {
                    overlay.remove();
                }
            };
        }
        listItem.appendChild(button);
        menuList.appendChild(listItem);
    });

    const cancelButton = document.createElement('button');
    cancelButton.textContent = 'Cancel';
    cancelButton.style.cssText = `
        width: 100%; padding: 12px 16px; border: none; background-color: #f0f0f0;
        border-radius: 8px; margin-top: 10px; font-size: 17px; font-weight: bold;
        color: #007aff; cursor: pointer;
    `;
    cancelButton.onclick = () => overlay.remove();

    menu.appendChild(menuList);
    menu.appendChild(cancelButton);
    overlay.appendChild(menu);
    document.body.appendChild(overlay);

    const firstButton = menu.querySelector('button');
    if (firstButton) firstButton.focus();

    overlay.onclick = (e) => {
        if (e.target === overlay) {
            overlay.remove();
        }
    };
}

function showAccessibleContextMenu(nativeMenuItems, title) {
    const nativeMenuBackdrop = document.querySelector('.Menu.in-portal .backdrop');
    if (nativeMenuBackdrop) {
        nativeMenuBackdrop.click();
    }

    const menuTitle = title || 'Actions';
    const items = Array.from(nativeMenuItems).map(item => ({
        text: item.textContent.trim(),
        action: () => simulateUniversalClick(item),
        isDestructive: item.classList.contains('destructive'),
        disabled: false
    })).filter(item => item.text);

    if (items.length > 0) {
        createAccessibleMenu(menuTitle, items);
    }
}
// =========================================================================
// SECTION: Delegated Event Listeners
// ...
// (All Delegated Event Listener functions are unchanged)
function addDelegatedChatListListeners() {
    const leftColumn = document.getElementById('LeftColumn');
    if (!leftColumn || leftColumn.hasAttribute('data-delegated-chat-listeners-added')) {
        return;
    }

    let longPressTimer;
    let wasLongPress = false;
    let touchStartX = 0;
    let touchStartY = 0;
    const LONG_PRESS_THRESHOLD = 10;
    const LONG_PRESS_DELAY = 500;

    const getTargetChat = (event) => event.target.closest('.ListItem.Chat');

    const showMenu = (chat, clientX, clientY) => {
        if (!chat) return;
        chat.dispatchEvent(new MouseEvent('contextmenu', {
            bubbles: true, cancelable: true, view: window, clientX, clientY
        }));
        setTimeout(() => {
            const nativeMenuItems = document.querySelectorAll('.ListItem-context-menu .MenuItem');
            if (nativeMenuItems.length > 0) {
                showAccessibleContextMenu(nativeMenuItems, 'Chat Actions');
            }
        }, 50);
    };

    leftColumn.addEventListener('touchstart', (event) => {
        const targetChat = getTargetChat(event);
        if (!targetChat) return;

        wasLongPress = false;
        touchStartX = event.touches[0].clientX;
        touchStartY = event.touches[0].clientY;

        longPressTimer = setTimeout(() => {
            wasLongPress = true;
            showMenu(targetChat, touchStartX, touchStartY);
        }, LONG_PRESS_DELAY);
    }, { passive: true });

    leftColumn.addEventListener('touchmove', (event) => {
        if (!longPressTimer) return;
        const deltaX = Math.abs(event.touches[0].clientX - touchStartX);
        const deltaY = Math.abs(event.touches[0].clientY - touchStartY);
        if (deltaX > LONG_PRESS_THRESHOLD || deltaY > LONG_PRESS_THRESHOLD) {
            clearTimeout(longPressTimer);
            longPressTimer = null;
        }
    }, { passive: true });

    leftColumn.addEventListener('touchend', (event) => {
        if (longPressTimer) {
            clearTimeout(longPressTimer);
            longPressTimer = null;
        }
        if (wasLongPress) {
            event.preventDefault();
        }
    });

    leftColumn.addEventListener('contextmenu', (e) => {
        const targetChat = getTargetChat(e);
        if (!targetChat) return;
        e.preventDefault();
        e.stopPropagation();
        showMenu(targetChat, e.clientX, e.clientY);
    });

    leftColumn.setAttribute('data-delegated-chat-listeners-added', 'true');
}

function addDelegatedMessageListeners() {
    const messageList = document.querySelector('.MessageList.custom-scroll');
    if (!messageList || messageList.hasAttribute('data-delegated-listeners-added')) {
        return;
    }

    let longPressTimer;
    let wasLongPress = false;
    let touchStartX = 0;
    let touchStartY = 0;
    const LONG_PRESS_DELAY = 500;
    const TOUCH_MOVE_THRESHOLD = 10;

    const getTargetMessage = (event) => event.target.closest('.Message');

    messageList.addEventListener('touchstart', (event) => {
        const targetMessage = getTargetMessage(event);
        if (!targetMessage) return;

        wasLongPress = false;
        touchStartX = event.touches[0].clientX;
        touchStartY = event.touches[0].clientY;

        longPressTimer = setTimeout(() => {
            wasLongPress = true;
            targetMessage.dispatchEvent(new MouseEvent('contextmenu', {
                bubbles: true, cancelable: true, view: window,
                clientX: touchStartX, clientY: touchStartY
            }));
            setTimeout(() => {
                const nativeMenuItems = document.querySelectorAll('.ContextMenuContainer .MenuItem');
                if (nativeMenuItems.length > 0) {
                    showAccessibleContextMenu(nativeMenuItems, 'Message Actions');
                }
            }, 50);
        }, LONG_PRESS_DELAY);
    }, { passive: true });

    messageList.addEventListener('touchmove', (event) => {
        if (!longPressTimer) return;
        const deltaX = Math.abs(event.touches[0].clientX - touchStartX);
        const deltaY = Math.abs(event.touches[0].clientY - touchStartY);
        if (deltaX > TOUCH_MOVE_THRESHOLD || deltaY > TOUCH_MOVE_THRESHOLD) {
            clearTimeout(longPressTimer);
            longPressTimer = null;
        }
    }, { passive: true });

    messageList.addEventListener('touchend', (event) => {
        if (longPressTimer) {
            clearTimeout(longPressTimer);
            longPressTimer = null;
        }
        if (wasLongPress) {
            event.preventDefault();
        }
    });

    messageList.addEventListener('contextmenu', (e) => {
        const targetMessage = getTargetMessage(e);
        if (!targetMessage) return;

        e.preventDefault();
        e.stopPropagation();

        targetMessage.dispatchEvent(new MouseEvent('contextmenu', {
            bubbles: true, cancelable: true, view: window,
            clientX: e.clientX, clientY: e.clientY
        }));

        setTimeout(() => {
            const nativeMenuItems = document.querySelectorAll('.ContextMenuContainer .MenuItem');
            if (nativeMenuItems.length > 0) showAccessibleContextMenu(nativeMenuItems, 'Message Actions');
        }, 50);
    });

    messageList.setAttribute('data-delegated-listeners-added', 'true');
}

function showAudioPlayerMenu() {
    const audioPlayer = document.querySelector('.AudioPlayer');
    if (!audioPlayer) return;

    const playPauseBtn = audioPlayer.querySelector('.toggle-play');
    const prevBtn = audioPlayer.querySelector('.player-button[aria-label="Previous track"]');
    const nextBtn = audioPlayer.querySelector('.player-button[aria-label="Next track"]');
    const rateBtn = audioPlayer.querySelector('.playback-button');
    const closeBtn = audioPlayer.querySelector('.player-close');

    const createRefreshAction = (element) => () => {
        if (element) simulateUniversalClick(element);
        setTimeout(showAudioPlayerMenu, 150);
    };

    const items = [{
        text: playPauseBtn && playPauseBtn.classList.contains('pause') ? 'Pause' : 'Play',
        action: createRefreshAction(playPauseBtn),
        disabled: !playPauseBtn,
        keepMenuOpen: true
    }, {
        text: 'Previous Track',
        action: createRefreshAction(prevBtn),
        disabled: !prevBtn || prevBtn.classList.contains('disabled'),
        keepMenuOpen: true
    }, {
        text: 'Next Track',
        action: createRefreshAction(nextBtn),
        disabled: !nextBtn || nextBtn.classList.contains('disabled'),
        keepMenuOpen: true
    }, {
        text: `Playback Rate (${rateBtn ? rateBtn.textContent.trim() : '1X'})`,
        action: createRefreshAction(rateBtn),
        disabled: !rateBtn,
        keepMenuOpen: true
    }, {
        text: 'Close Player',
        action: () => simulateUniversalClick(closeBtn),
        disabled: !closeBtn,
        isDestructive: true
    }];

    createAccessibleMenu('Player Controls', items);
}
// =========================================================================
// SECTION: Message Processing
// ...
// (All Message Processing functions are unchanged)
function handleAudioMessage(message, isVoice) {
    const originalContentWrapper = message.querySelector('.message-content-wrapper');
    if (!originalContentWrapper || message.querySelector('[data-accessible-audio-heading="true"]')) {
        return;
    }

    const playButton = message.querySelector('.Button.toggle-play');
    if (!playButton) return;

    const textContentElem = message.querySelector('.text-content');
    let captionText = '';
    const extractedLinks = [];
    if (textContentElem) {
        const clone = textContentElem.cloneNode(true);
        clone.querySelectorAll('.Reactions, .MessageMeta').forEach(el => el.remove());
        clone.querySelectorAll('img.emoji, .custom-emoji').forEach(emojiEl => {
            const altText = emojiEl.alt || emojiEl.dataset.alt;
            if (altText) emojiEl.parentNode.replaceChild(document.createTextNode(altText), emojiEl);
        });
        captionText = clone.innerText.trim();
        textContentElem.querySelectorAll('a').forEach(link => extractedLinks.push(link));
    }

    const accessibleHeading = document.createElement('div');
    accessibleHeading.setAttribute('role', 'heading');
    accessibleHeading.setAttribute('aria-level', '3');
    accessibleHeading.setAttribute('tabindex', '0');
    accessibleHeading.setAttribute('data-accessible-audio-heading', 'true');
    accessibleHeading.style.cssText = 'padding: 12px 16px; cursor: pointer; font-size: 1rem; border: 1px solid transparent; border-radius: 8px; margin: 2px;';
    accessibleHeading.onfocus = () => accessibleHeading.style.borderColor = '#3390ec';
    accessibleHeading.onblur = () => accessibleHeading.style.borderColor = 'transparent';

    const senderTitleElem = message.querySelector('.message-title .sender-title');
    const sender = senderTitleElem ? `From ${senderTitleElem.textContent.trim()}.` : (message.classList.contains('own') ? 'Your message.' : '');

    let label, text;

    const controlsContainer = document.createElement('div');
    controlsContainer.setAttribute('role', 'toolbar');
    controlsContainer.setAttribute('aria-label', 'Audio Message Options');
    controlsContainer.style.cssText = 'margin-top: 8px; display: flex; flex-wrap: wrap; gap: 8px; z-index: 6; position: relative;';


    if (isVoice) {
        const voiceDurationElem = message.querySelector('.voice-duration');
        const duration = (voiceDurationElem && voiceDurationElem.textContent.trim()) || '';
        const timeElem = message.querySelector('.message-time');
        const time = (timeElem && timeElem.textContent.trim()) || '';
        label = `${sender} Voice message. ${duration ? `Duration: ${duration}.` : ''} ${captionText ? `Caption: ${captionText}.` : ''} ${time ? `Time: ${time}.` : ''} Double tap to play and open player controls.`;
        text = `Voice message (${duration})`;
        accessibleHeading.setAttribute('aria-label', label);
        accessibleHeading.textContent = text;
        accessibleHeading.onclick = (e) => {
            e.stopPropagation();
            simulateUniversalClick(playButton);
            setTimeout(showAudioPlayerMenu, 150);
        };
        message.insertBefore(accessibleHeading, originalContentWrapper);
    } else { // Music
        const songTitleElem = message.querySelector('.Audio .title');
        const songTitle = (songTitleElem && songTitleElem.textContent.trim()) || 'Untitled';
        const performerElem = message.querySelector('.Audio .performer');
        const performer = (performerElem && performerElem.textContent.trim()) || 'Unknown Artist';
        const durationElem = message.querySelector('.Audio .duration');
        const duration = (durationElem && durationElem.textContent.trim());
        label = `Music file. ${performer} - ${songTitle}. ${duration ? `Duration: ${duration}.` : ''} ${captionText ? `Caption: ${captionText}.` : ''} Double tap to play and open player controls.`;
        text = `Music: ${songTitle}`;

        accessibleHeading.setAttribute('aria-label', label);
        accessibleHeading.textContent = text;
        accessibleHeading.onclick = (e) => {
            e.stopPropagation();
            simulateUniversalClick(playButton);
            setTimeout(showAudioPlayerMenu, 150);
        };
        message.insertBefore(accessibleHeading, originalContentWrapper);

        const originalDownloadContainer = message.querySelector('.Audio .download-button');
        if (originalDownloadContainer) {
            const newDownloadButton = document.createElement('button');

            const updateButtonState = () => {
                const isDownloading = originalDownloadContainer.querySelector('.Progress, .spinner-container');
                const nativeCancelButton = originalDownloadContainer.querySelector('.cancel-button, .icon-close');

                if (isDownloading || nativeCancelButton) {
                    newDownloadButton.textContent = 'Cancel';
                    newDownloadButton.onclick = (e) => {
                        e.stopPropagation();
                        if (nativeCancelButton) simulateUniversalClick(nativeCancelButton);
                    };
                } else {
                    newDownloadButton.textContent = `Download ${performer} - ${songTitle}`;
                    newDownloadButton.onclick = (e) => {
                        e.stopPropagation();
                        simulateUniversalClick(originalDownloadContainer);
                    };
                }
            };
            updateButtonState();
            const observer = new MutationObserver(updateButtonState);
            observer.observe(originalDownloadContainer, { childList: true, subtree: true, attributes: true });

            controlsContainer.appendChild(newDownloadButton);
        }
    }

    if (extractedLinks.length > 0) {
        extractedLinks.forEach(link => {
            const newLinkAnchor = document.createElement('a');
            newLinkAnchor.textContent = link.textContent.trim() || link.href;
            newLinkAnchor.href = link.href;
            newLinkAnchor.target = '_blank';
            newLinkAnchor.rel = 'noopener noreferrer';
            newLinkAnchor.style.cssText = 'padding: 8px 12px; background-color: #f0f0f0; border: 1px solid #ddd; border-radius: 8px; text-decoration: none; color: #007aff;';
            controlsContainer.appendChild(newLinkAnchor);
        });
    }

    if (controlsContainer.hasChildNodes()) {
        message.insertBefore(controlsContainer, accessibleHeading.nextSibling);
    }

    originalContentWrapper.querySelectorAll('button, a, [role="button"]').forEach(el => {
        el.setAttribute('aria-hidden', 'true');
        el.setAttribute('tabindex', '-1');
    });

    message.setAttribute('data-accessible-message', 'true');

    if (!isInitialPageLoad) {
        const announceLabel = accessibleHeading.getAttribute('aria-label');
        if (announceLabel) {
            announceText(announceLabel, true);
        }
    }
}

function processMessages() {
    const messages = document.querySelectorAll('.Message:not([data-accessible-message="true"])');
    messages.forEach(message => {
        const isVoice = !!message.querySelector('.message-content.voice');
        const isMusic = !!message.querySelector('.Audio.inline');
        if (isVoice || isMusic) {
            handleAudioMessage(message, isVoice);
            return;
        }

        const messageContentWrapper = message.querySelector('.message-content-wrapper');
        if (!messageContentWrapper) return;

        message.style.display = 'flex';
        message.style.flexDirection = 'column';
        message.style.alignItems = 'flex-start';

        const overlay = document.createElement('div');
        overlay.setAttribute('role', 'heading');
        overlay.setAttribute('aria-level', '3');
        overlay.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 5; cursor: pointer;';

        const labelParts = [];

        const stickerImage = message.querySelector('.sticker-image');
        const gifMedia = message.querySelector('.message-media-gif, .media-inner.is-document.is-animated');

        if (stickerImage) {
            const emojiEl = stickerImage.closest('.Message').querySelector('.emoji-small');
            const stickerEmoji = emojiEl ? emojiEl.alt || emojiEl.textContent.trim() : '';
            let stickerLabel = 'Sticker';
            if (stickerEmoji) {
                stickerLabel += `: ${stickerEmoji}`;
            }
            labelParts.push(stickerLabel);
            stickerImage.setAttribute('aria-label', stickerLabel);
            stickerImage.setAttribute('alt', stickerLabel);
        } else if (gifMedia) {
            let gifLabel = 'Animated GIF';
            const messageTextElem = message.querySelector('.message-text');
            const caption = (messageTextElem && messageTextElem.textContent.trim()) || '';
            if (caption) {
                gifLabel += `: ${caption}`;
            }
            labelParts.push(gifLabel);
            const mediaElement = gifMedia.querySelector('img, video');
            if (mediaElement) {
                mediaElement.setAttribute('aria-label', gifLabel);
                mediaElement.setAttribute('alt', gifLabel);
            }
        }

        const isOwnMessage = message.classList.contains('own');
        if (isOwnMessage) {
            labelParts.push('Your message.');
        } else {
            const senderTitleElem = message.querySelector('.message-title .sender-title');
            const isForwarded = !!message.querySelector('.message-title-wrapper .label');
            if (senderTitleElem) {
                labelParts.push(isForwarded ? `Forwarded from ${senderTitleElem.textContent.trim()}.` : `From ${senderTitleElem.textContent.trim()}.`);
            }
        }

        const textContentElem = message.querySelector('.text-content');
        if (textContentElem) {
            const clone = textContentElem.cloneNode(true);
            clone.querySelectorAll('.Reactions, .MessageMeta').forEach(el => el.remove());
            clone.querySelectorAll('img.emoji, .custom-emoji').forEach(emojiEl => {
                const altText = emojiEl.alt || emojiEl.dataset.alt;
                if (altText) emojiEl.parentNode.replaceChild(document.createTextNode(altText), emojiEl);
            });
            const cleanText = clone.innerText.trim();
            if (cleanText) labelParts.push(cleanText);
        }

        const controlsContainer = document.createElement('div');
        controlsContainer.setAttribute('role', 'toolbar');
        controlsContainer.setAttribute('aria-label', 'Message Options');
        controlsContainer.style.cssText = 'margin-top: 8px; display: flex; flex-wrap: wrap; gap: 8px; z-index: 6; position: relative;';

        if (textContentElem) {
            textContentElem.querySelectorAll('a').forEach(link => {
                const newLinkAnchor = document.createElement('a');
                newLinkAnchor.textContent = link.textContent.trim() || link.href;
                newLinkAnchor.href = link.href;
                newLinkAnchor.target = '_blank';
                newLinkAnchor.rel = 'noopener noreferrer';
                controlsContainer.appendChild(newLinkAnchor);
            });
        }

        const replyHeader = message.querySelector('.message-subheader .EmbeddedMessage');
        if (replyHeader) {
            const embeddedSenderElem = replyHeader.querySelector('.embedded-sender');
            const sender = (embeddedSenderElem && embeddedSenderElem.textContent.trim());
            const embeddedTextElem = replyHeader.querySelector('.embedded-text-wrapper');
            const text = (embeddedTextElem && embeddedTextElem.textContent.trim());
            let replyLabel = "In reply to";
            if (sender) replyLabel += ` message from ${sender}`;
            if (text) replyLabel += `: ${text}`;
            labelParts.push(`${replyLabel}.`);
        }

        message.querySelectorAll('.InlineButtons .Button, .reply-markup .Button').forEach(button => {
            const buttonText = button.textContent.trim();
            if (buttonText) {
                const newInlineButton = document.createElement('button');
                newInlineButton.textContent = `Button: ${buttonText}`;
                newInlineButton.onclick = (e) => { e.stopPropagation(); simulateUniversalClick(button); };
                controlsContainer.appendChild(newInlineButton);
            }
        });

        const originalForwardButton = message.querySelector('.message-action-button[aria-label="Forward"]');
        if (originalForwardButton) {
            const newForwardButton = document.createElement('button');
            newForwardButton.textContent = 'Forward';
            newForwardButton.onclick = (e) => { e.stopPropagation(); simulateUniversalClick(originalForwardButton); };
            controlsContainer.appendChild(newForwardButton);
        }

        const originalCommentButton = message.querySelector('.CommentButton');
        if (originalCommentButton) {
            const newCommentButton = document.createElement('button');
            const commentCount = originalCommentButton.getAttribute('data-cnt');
            const labelElem = originalCommentButton.querySelector('.label');
            const labelText = (labelElem && labelElem.textContent.trim());
            let buttonText;

            if (labelText && labelText.toLowerCase().includes('leave a comment')) {
                buttonText = 'Leave a comment';
            } else if (commentCount && parseInt(commentCount, 10) > 0) {
                buttonText = `${commentCount} ${parseInt(commentCount, 10) === 1 ? 'comment' : 'comments'}`;
            } else {
                buttonText = 'Comments';
            }

            newCommentButton.textContent = buttonText;
            newCommentButton.onclick = (e) => {
                e.stopPropagation();
                simulateUniversalClick(originalCommentButton);
            };
            controlsContainer.appendChild(newCommentButton);
        }

        const album = message.querySelector('.Album');
        const fileInfo = message.querySelector('.File');

        if (album) {
            const items = Array.from(album.querySelectorAll('.media-inner'));
            let photoCount = 0, videoCount = 0;
            items.forEach(item => {
                if (item.querySelector('video, .message-media-duration')) videoCount++;
                else photoCount++;
            });
            let albumDescription = `Album with ${items.length} items`;
            if (photoCount > 0) albumDescription += `: ${photoCount} photos`;
            if (videoCount > 0) albumDescription += `${photoCount > 0 ? ' and' : ':'} ${videoCount} videos`;
            albumDescription += '.';

            let currentIndex = 0;
            const getSelectedItemType = () => (items[currentIndex] && items[currentIndex].querySelector('video, .message-media-duration')) ? 'Video' : 'Photo';
            const updateMediaFocus = () => {
                items.forEach((item, index) => item.style.outline = index === currentIndex ? '3px solid #3390ec' : 'none');
                const currentItemLabel = `Item ${currentIndex + 1} of ${items.length}: ${getSelectedItemType()}.`;
                overlay.setAttribute('aria-label', `${currentItemLabel} ${albumDescription} \n${labelParts.join(' \n')}`);
            };
            overlay.onclick = (e) => { e.stopPropagation(); if (items[currentIndex]) simulateUniversalClick(items[currentIndex]); };
            const prevButton = document.createElement('button');
            prevButton.textContent = 'Previous Media';
            prevButton.onclick = () => { currentIndex = (currentIndex - 1 + items.length) % items.length; updateMediaFocus(); };
            controlsContainer.appendChild(prevButton);
            const nextButton = document.createElement('button');
            nextButton.textContent = 'Next Media';
            nextButton.onclick = () => { currentIndex = (currentIndex + 1) % items.length; updateMediaFocus(); };
            controlsContainer.appendChild(nextButton);
            updateMediaFocus();
        } else if (fileInfo) {
            labelParts.unshift("File.");
            const fileNameElem = fileInfo.querySelector('.file-title');
            const fileName = (fileNameElem && fileNameElem.textContent.trim());
            const fileSizeElem = fileInfo.querySelector('.file-subtitle');
            const fileSize = (fileSizeElem && fileSizeElem.textContent.trim());
            if (fileName) labelParts.push(`File name: ${fileName}.`);
            if (fileSize) labelParts.push(`File size: ${fileSize}.`);
            const downloadTrigger = fileInfo.querySelector('.file-icon-container');
            if (downloadTrigger) {
                overlay.onclick = (e) => { e.stopPropagation(); simulateUniversalClick(downloadTrigger); };
                const clone = downloadTrigger.cloneNode(true);
                clone.removeAttribute('aria-hidden');
                clone.setAttribute('tabindex', '0');
                clone.setAttribute('role', 'button');
                clone.setAttribute('aria-label', `Download ${fileName || 'file'}`);
                clone.style.position = 'relative';
                clone.onclick = (e) => { e.stopPropagation(); simulateUniversalClick(downloadTrigger); };
                controlsContainer.appendChild(clone);
            }
        } else {
            const hasVideoIndicator = message.querySelector('video, .message-media-duration, .icon-large-play');
            const mediaContainer = message.querySelector('.media-inner');
            const hasPhotoIndicator = mediaContainer && !hasVideoIndicator;
            if (hasVideoIndicator) labelParts.unshift("Video.");
            else if (hasPhotoIndicator) labelParts.unshift("Photo.");

            if (mediaContainer) {
                overlay.onclick = (e) => {
                    e.stopPropagation();
                    const interactiveMedia = message.querySelector('.media-inner.interactive');
                    if (interactiveMedia) simulateUniversalClick(interactiveMedia);
                };
            }
        }

        const timeElem = message.querySelector('.message-time');
        if (timeElem) {
            let metaText = `Time: ${timeElem.textContent.trim()}.`;
            if (isOwnMessage) {
                const statusIcon = message.querySelector('.MessageOutgoingStatus i');
                if (statusIcon) {
                    if (statusIcon.classList.contains('icon-message-read')) metaText += ' Status: Read.';
                    else if (statusIcon.classList.contains('icon-message-succeeded')) metaText += ' Status: Sent, not yet seen.';
                    else if (statusIcon.classList.contains('icon-clock')) metaText += ' Status: Sending.';
                    else if (statusIcon.classList.contains('icon-error')) metaText += ' Status: Failed to send.';
                }
            }
            labelParts.push(metaText);
        }

        const viewsElem = message.querySelector('.message-views');
        if (viewsElem) labelParts.push(`${viewsElem.textContent.trim()} views.`);

        if (!album) {
            overlay.setAttribute('aria-label', labelParts.join(' \n'));
        }
        messageContentWrapper.style.position = 'relative';
        messageContentWrapper.appendChild(overlay);
        if (controlsContainer.hasChildNodes()) message.appendChild(controlsContainer);

        const allInnerElements = messageContentWrapper.querySelectorAll('button, a, [role="button"], input, .Reactions, .sticker-image, .message-media-gif, .media-inner.is-document.is-animated');
        allInnerElements.forEach(el => {
            if (el !== overlay && el.closest('.message-content-wrapper') === messageContentWrapper) {
                el.setAttribute('aria-hidden', 'true');
                el.setAttribute('tabindex', '-1');
            }
        });

        overlay.removeAttribute('aria-hidden');
        overlay.setAttribute('tabindex', '0');
        message.setAttribute('data-accessible-message', 'true');

        if (!isInitialPageLoad) {
            const messageList = document.querySelector('.MessageList.custom-scroll');
            const allVisibleMessages = messageList ? Array.from(messageList.querySelectorAll('.Message:not(.service-message)')) : [];
            const latestMessage = allVisibleMessages[allVisibleMessages.length - 1];

            if (latestMessage === message && labelParts.length > 0) {
                const announcementText = labelParts.join(' \n');
                const messageId = message.id || message.dataset.messageId;

                if (messageId && messageId !== lastAnnouncedMessageId) {
                    announceText(announcementText, true);
                    lastAnnouncedMessageId = messageId;
                } else if (!messageId && announcementText !== unifiedLiveRegion.textContent) {
                    announceText(announcementText, true);
                    lastAnnouncedMessageId = null;
                }
            }
        }
    });
}
// =========================================================================
// SECTION: Other UI Processors
// =========================================================================

// NEW FUNCTION to handle confirmation dialogs like the delete message dialog
function processConfirmDialog() {
    const dialog = document.querySelector('.ConfirmDialog');
    if (!dialog || dialog.hasAttribute('data-accessible-dialog')) {
        return;
    }

    const titleElem = dialog.querySelector('h3');
    dialog.setAttribute('role', 'dialog');
    dialog.setAttribute('aria-modal', 'true');

    if (titleElem && !dialog.hasAttribute('aria-labelledby')) {
        const titleId = 'dialog-title-' + Date.now();
        titleElem.id = titleId;
        dialog.setAttribute('aria-labelledby', titleId);
    }

    const checkbox = dialog.querySelector('input[type="checkbox"]');
    if (checkbox) {
        const label = checkbox.closest('label');
        if (label && !checkbox.hasAttribute('aria-label')) {
            const labelText = label.textContent.trim();
            if (labelText) {
                checkbox.setAttribute('aria-label', labelText);
            }
        }
    }

    dialog.setAttribute('data-accessible-dialog', 'true');

    // Set focus to the first interactive element (checkbox or delete button)
    const elementToFocus = checkbox || dialog.querySelector('.Button.danger');
    if (elementToFocus) {
        setTimeout(() => elementToFocus.focus(), 100);
    }
}

function processForwardList() {
    const forwardModal = document.querySelector('.Modal.ChatOrUserPicker');
    if (!forwardModal) return;
    const items = forwardModal.querySelectorAll('.ChatOrUserPicker-item:not([data-accessible-forward-item="true"])');
    items.forEach(item => {
        const nameElem = item.querySelector('.fullName');
        const subtitleElem = item.querySelector('.OYmzCSp2');
        if (!nameElem) return;

        const name = nameElem.textContent.trim();
        const subtitle = subtitleElem ? subtitleElem.textContent.trim() : '';
        const subtitleLower = subtitle.toLowerCase();
        const avatar = item.querySelector('.Avatar');
        let itemTypePrefix = '';

        if (avatar && avatar.classList.contains('saved-messages')) itemTypePrefix = '';
        else if (subtitleLower.includes('subscriber')) itemTypePrefix = `Channel: ${name}. `;
        else if (subtitleLower.includes('member') || (avatar && avatar.classList.contains('forum'))) itemTypePrefix = `Group: ${name}. `;
        else if (subtitleLower.includes('bot')) itemTypePrefix = `Bot: ${name}. `;
        else if (subtitleLower.includes('last seen') || subtitleLower.includes('online') || subtitleLower.includes('monthly users') || (avatar && avatar.classList.contains('private'))) itemTypePrefix = `Chat with ${name}. `;
        else if (subtitle) itemTypePrefix = `Group: ${name}. `;
        else itemTypePrefix = `Chat with ${name}. `;

        const newLabel = itemTypePrefix ? `${itemTypePrefix}${subtitle}` : `${name}. ${subtitle}`;
        item.setAttribute('aria-label', newLabel);
        item.setAttribute('data-accessible-forward-item', 'true');
    });
}

function processSearchResults() {
    const searchContainer = document.querySelector('.LeftSearch--content');
    if (!searchContainer) return;
    const items = searchContainer.querySelectorAll('.ListItem.search-result .ListItem-button:not([data-accessible-search-item="true"])');
    items.forEach(item => {
        const nameElem = item.querySelector('.fullName');
        const statusElem = item.querySelector('.status');
        if (!nameElem || !statusElem) return;
        const name = nameElem.textContent.trim();
        const statusText = statusElem.textContent.trim();
        const statusTextLower = statusText.toLowerCase();
        let typePrefix = '';
        if (statusTextLower.includes('subscriber')) typePrefix = `Channel: ${name}. `;
        else if (statusTextLower.includes('member')) typePrefix = `Group: ${name}. `;
        else if (statusTextLower.includes('bot')) typePrefix = `Bot: ${name}. `;
        else if (statusTextLower.includes('last seen') || statusTextLower.includes('online')) typePrefix = `User: ${name}. `;
        else typePrefix = `${name}. `;
        const ariaLabel = `${typePrefix}${statusText}`;
        item.setAttribute('aria-label', ariaLabel);
        item.setAttribute('data-accessible-search-item', 'true');
    });
}

// =========================================================================
// SECTION: Settings Accessibility
// ...
// (The function processSettings is unchanged)
let settingsHeaderIdCounter = 0;

function processSettings() {
    // Target only the currently visible settings page
    const activeSettingsContent = document.querySelector('#Settings .Transition_slide-active .settings-content');
    if (!activeSettingsContent) return;

    // Group items with headers (like Data and Storage sections)
    const settingItems = activeSettingsContent.querySelectorAll('.settings-item:not([data-accessible-group="true"])');
    settingItems.forEach(item => {
        const header = item.querySelector('h4.settings-item-header');
        if (header) {
            const headerId = `settings-header-${settingsHeaderIdCounter++}`;
            header.id = headerId;
            item.setAttribute('role', 'group');
            item.setAttribute('aria-labelledby', headerId);

            // Process checkboxes within this group
            item.querySelectorAll('label.Checkbox').forEach(label => {
                const input = label.querySelector('input[type="checkbox"]');
                const span = label.querySelector('span.label');
                if (input && span && !input.hasAttribute('aria-label')) {
                    const fullLabel = `${header.textContent.trim()}: ${span.textContent.trim()}`;
                    input.setAttribute('aria-label', fullLabel);
                }
            });

            // Process range sliders within this group
            const rangeSlider = item.querySelector('.RangeSlider');
            if (rangeSlider) {
                const rangeInput = rangeSlider.querySelector('input[type="range"]');
                const rangeLabel = rangeSlider.querySelector('.slider-top-row .label');
                const rangeValue = rangeSlider.querySelector('.slider-top-row .value');
                if (rangeInput && rangeLabel && !rangeInput.hasAttribute('aria-label')) {
                    rangeInput.setAttribute('aria-label', rangeLabel.textContent.trim());
                }
                if (rangeInput && rangeValue) {
                    rangeInput.setAttribute('aria-valuetext', rangeValue.textContent.trim());
                }
            }
        }
        item.setAttribute('data-accessible-group', 'true');
    });

    // Process simple navigation list items in settings (main settings page)
    const simpleItems = activeSettingsContent.querySelectorAll('.ListItem:not([data-accessible-setting="true"]) .ListItem-button');
    simpleItems.forEach(button => {
        // Only process if it doesn't have a checkbox inside, to avoid duplication.
        if (button.querySelector('input[type="checkbox"]')) return;

        const textContent = button.textContent.trim();
        const currentValueElem = button.querySelector('.settings-item__current-value');
        if (currentValueElem) {
            const currentValue = currentValueElem.textContent.trim();
            const baseText = textContent.replace(currentValue, '').trim();
            button.setAttribute('aria-label', `${baseText}: ${currentValue}`);
        }
        button.closest('.ListItem').setAttribute('data-accessible-setting', 'true');
    });
}
// =========================================================================
// Other utility functions are unchanged
// ...
function processUnreadDivider() {
    const unreadDividers = document.querySelectorAll('.unread-divider:not([role="heading"])');
    unreadDividers.forEach(divider => {
        divider.setAttribute('role', 'heading');
        divider.setAttribute('aria-level', '2');
        divider.setAttribute('tabindex', '0');
    });
}

function cleanupExtraButtons() {
    const selectToolbar = document.querySelector('.MessageSelectToolbar');
    if (selectToolbar && !selectToolbar.hasAttribute('data-hidden-by-script')) {
        selectToolbar.setAttribute('aria-hidden', 'true');
        selectToolbar.setAttribute('data-hidden-by-script', 'true');
    }
    const mentionButton = document.querySelector('button[aria-label="Go to next mention"]');
    if (mentionButton) mentionButton.parentElement.setAttribute('aria-hidden', 'true');
    const reactionButton = document.querySelector('button[aria-label="Go to next unread reactions"]');
    if (reactionButton) reactionButton.parentElement.setAttribute('aria-hidden', 'true');
}

function hideInvisibleElements() {
    const cloneInput = document.querySelector('.custom-scroll.input-scroller.clone');
    if (cloneInput && !cloneInput.hasAttribute('aria-hidden')) {
        cloneInput.setAttribute('aria-hidden', 'true');
        cloneInput.setAttribute('tabindex', '-1');
    }

    const placeholderTextElem = document.querySelector('#message-input-text .placeholder-text');
    if (placeholderTextElem && !placeholderTextElem.hasAttribute('aria-hidden')) {
        placeholderTextElem.setAttribute('aria-hidden', 'true');
        placeholderTextElem.setAttribute('tabindex', '-1');
    }
}

function addVideoViewerToggleListener() {
    document.body.addEventListener('click', function(event) {
        const mediaViewer = event.target.closest('#MediaViewer');
        if (!mediaViewer) return;
        if (event.target.closest('.VideoPlayer') && !event.target.closest('.VideoPlayerControls')) {
            const video = mediaViewer.querySelector('video#media-viewer-video');
            if (video) video.paused ? video.play() : video.pause();
        }
    });
}

function addGoToBottomListener() {
    document.body.addEventListener('click', function(event) {
        const goToBottomButton = event.target.closest('button[aria-label="Go to bottom"]');
        if (!goToBottomButton) return;
        event.preventDefault();
        event.stopPropagation();
        const messageList = document.querySelector('.MessageList.custom-scroll');
        if (messageList) {
            messageList.scrollTo({ top: messageList.scrollHeight, behavior: 'smooth' });
        }
    }, true);
}
// =========================================================================
// SECTION: Main Observer
// =========================================================================
function startObserver() {
    const targetNode = document.getElementById('root');
    if (!targetNode) {
        setTimeout(startObserver, 500);
        return;
    }

    createUnifiedLiveRegion();

    const observer = new MutationObserver((mutationsList) => {
        const processorsToRun = new Set();

        for (const mutation of mutationsList) {
            if (!(mutation.target instanceof Element)) continue;

            if (mutation.target.closest('#LeftColumn')) {
                processorsToRun.add(processChatList);
                processorsToRun.add(addDelegatedChatListListeners);
                processorsToRun.add(processSearchResults);
            }

            if (mutation.target.closest('.MessageList')) {
                processorsToRun.add(processMessages);
                processorsToRun.add(processUnreadDivider);
            }

            if (mutation.target.closest('.Modal.ChatOrUserPicker')) {
                processorsToRun.add(processForwardList);
            }
            
            // MODIFIED: Added a check for the confirmation dialog
            if (mutation.target.closest('.ConfirmDialog')) {
                processorsToRun.add(processConfirmDialog);
            }

            if (mutation.target.closest('.ChatInfo')) {
                processorsToRun.add(monitorChatStatus);
            }

            if (mutation.target.closest('#Settings')) {
                processorsToRun.add(processSettings);
            }

            if (mutation.type === 'childList' && Array.from(mutation.addedNodes).some(node => node instanceof Element && (node.matches('.custom-scroll.input-scroller.clone') || node.matches('.placeholder-text')))) {
                processorsToRun.add(hideInvisibleElements);
            }
        }

        if (processorsToRun.size > 0) {
            if (window.accessibilityTimeout) clearTimeout(window.accessibilityTimeout);
            window.accessibilityTimeout = setTimeout(() => {
                processorsToRun.forEach(processor => processor());
                cleanupExtraButtons();
                hideInvisibleElements();
                if (processorsToRun.has(processMessages)) {
                    addDelegatedMessageListeners();
                }
            }, 250);
        }
    });

    // Added `attributes: true` to detect class changes for settings navigation.
    observer.observe(targetNode, { childList: true, subtree: true, attributes: true });

    addVideoViewerToggleListener();
    addGoToBottomListener();

    // Initial run of all processors on page load.
    setTimeout(() => {
        processChatList();
        addDelegatedChatListListeners();
        isInitialPageLoad = true;
        processMessages();
        addDelegatedMessageListeners();
        processUnreadDivider();
        isInitialPageLoad = false;
        cleanupExtraButtons();
        monitorChatStatus();
        hideInvisibleElements();
        processSettings();
    }, 1000);
}

window.addEventListener('load', startObserver);
})();