Telegram Ultimate Enhancer

Telegram Web 低开销增强:恢复有充分证据的原生下载/转发动作,解除受保护内容复制与右键限制,并提供剧透解除、倍速、画中画、外链净化和防误拨。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Telegram Ultimate Enhancer
// @name:zh-CN   Telegram 终极增强器
// @version      1.0.0
// @namespace    https://github.com/xiaohuitongxue88-ctrl/Telegram-Ultimate-Enhancer
// @description  Telegram Web 低开销增强:恢复有充分证据的原生下载/转发动作,解除受保护内容复制与右键限制,并提供剧透解除、倍速、画中画、外链净化和防误拨。
// @description:zh-CN Telegram Web 低开销增强:恢复有充分证据的原生下载/转发动作,解除受保护内容复制与右键限制,并提供剧透解除、倍速、画中画、外链净化和防误拨。
// @author       xiaohuitongxue
// @license      GNU GPLv3
// @homepageURL  https://github.com/xiaohuitongxue88-ctrl/Telegram-Ultimate-Enhancer
// @supportURL   https://github.com/xiaohuitongxue88-ctrl/Telegram-Ultimate-Enhancer/issues
// @match        https://web.telegram.org/*
// @match        https://webk.telegram.org/*
// @match        https://webz.telegram.org/*
// @icon         https://img.icons8.com/color/452/telegram-app--v5.png
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

/*
 * Telegram Ultimate Enhancer
 * Copyright (C) 2026 xiaohuitongxue
 * Licensed under GNU GPLv3.
 * Additional terms under GPLv3 Section 7 for project-authored material:
 * see https://github.com/xiaohuitongxue88-ctrl/Telegram-Ultimate-Enhancer/blob/main/ADDITIONAL_TERMS.md
 */

(function () {
    'use strict';

    // =============================================================================
    // 01. 核心配置
    // =============================================================================
    const Config = Object.freeze({
        VERSION: '1.0.0',
        DEBUG: false,

        HIDE_PINNED_MESSAGES: true,
        HIDE_SPONSORED_MESSAGES: true,
        UNLOCK_SPOILERS: true,
        UNLOCK_PROTECTED_COPY: true,
        OPEN_EXTERNAL_LINKS_IN_NEW_TAB: true,
        CLEAN_TRACKING_PARAMS_ON_COPY: true,
        CONFIRM_CALL_ACTION: true,

        FEEDBACK_PROMPT_ENABLED: true,
        FEEDBACK_TEST_MODE: false,
        FEEDBACK_TEST_DELAY_MS: 1500,
        FEEDBACK_IDLE_DELAY_MS: 10000,
        FEEDBACK_SESSION_GAP_MS: 30 * 60 * 1000,

        FEEDBACK_URL:
            'https://greasyfork.org/zh-CN/scripts/590834-telegram-ultimate-enhancer/feedback',
        GITHUB_URL:
            'https://github.com/xiaohuitongxue88-ctrl/Telegram-Ultimate-Enhancer',

        FEEDBACK_STAGES: Object.freeze([
            Object.freeze({
                minAgeMs: 3 * 24 * 60 * 60 * 1000,
                minSessions: 3,
                minSuccess: 5,
                minFeatures: 2,
                minGapMs: 0
            }),
            Object.freeze({
                minAgeMs: 14 * 24 * 60 * 60 * 1000,
                minSessions: 10,
                minSuccess: 25,
                minFeatures: 3,
                minGapMs: 10 * 24 * 60 * 60 * 1000
            }),
            Object.freeze({
                minAgeMs: 45 * 24 * 60 * 60 * 1000,
                minSessions: 25,
                minSuccess: 80,
                minFeatures: 3,
                minGapMs: 30 * 24 * 60 * 60 * 1000
            })
        ]),

        TRACKERS: Object.freeze([
            'utm_source',
            'utm_medium',
            'utm_campaign',
            'utm_term',
            'utm_content',
            'si',
            'igshid',
            'fbclid',
            'gclid',
            'share_id'
        ]),

        DOM_FLUSH_DELAY_MS: 28,
        DOM_MAX_ROOTS_PER_FLUSH: 24,
        DOM_QUEUE_SOFT_LIMIT: 96,

        TOAST_DURATION: 1600
    });

    // =============================================================================
    // 02. 日志
    // =============================================================================
    const Log = {
        info(tag, ...args) {
            if (Config.DEBUG) {
                console.log(
                    `[Telegram Ultimate Enhancer V${Config.VERSION}] [${tag}]`,
                    ...args
                );
            }
        },

        warn(tag, ...args) {
            if (Config.DEBUG) {
                console.warn(
                    `[Telegram Ultimate Enhancer V${Config.VERSION}] [${tag}]`,
                    ...args
                );
            }
        },

        error(tag, ...args) {
            console.error(
                `[Telegram Ultimate Enhancer V${Config.VERSION}] [${tag}]`,
                ...args
            );
        }
    };

    // =============================================================================
    // 03. 弱引用缓存
    // =============================================================================
    const Cache = {
        processedButtons: new WeakSet()
    };

    // =============================================================================
    // 04. 通用工具
    // =============================================================================
    const Utils = {
        isEditableTarget(target) {
            if (!(target instanceof Element)) return false;
            if (target.isContentEditable) return true;

            const tag = target.tagName;
            if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {
                return true;
            }

            return Boolean(
                target.closest(
                    'input,textarea,select,[contenteditable="true"],[role="textbox"]'
                )
            );
        },

        getActiveVideo() {
            const viewerVideo = document.querySelector(
                '.media-viewer-aspecter video,.media-viewer video'
            );
            if (viewerVideo) return viewerVideo;

            const videos = Array.from(document.querySelectorAll('video'));
            if (!videos.length) return null;

            let best = null;
            let bestVisibleArea = 0;

            for (const video of videos) {
                const rect = video.getBoundingClientRect();
                if (rect.width <= 1 || rect.height <= 1) continue;

                const style = getComputedStyle(video);
                if (
                    style.display === 'none' ||
                    style.visibility === 'hidden' ||
                    Number(style.opacity || 1) <= 0
                ) {
                    continue;
                }

                const vw =
                    window.innerWidth || document.documentElement.clientWidth;
                const vh =
                    window.innerHeight || document.documentElement.clientHeight;

                const visibleWidth = Math.max(
                    0,
                    Math.min(rect.right, vw) - Math.max(rect.left, 0)
                );
                const visibleHeight = Math.max(
                    0,
                    Math.min(rect.bottom, vh) - Math.max(rect.top, 0)
                );
                const area = visibleWidth * visibleHeight;

                if (area > bestVisibleArea) {
                    best = video;
                    bestVisibleArea = area;
                }
            }

            return best || videos[0];
        },

        selectionText() {
            try {
                return window.getSelection()?.toString().trim() || '';
            } catch {
                return '';
            }
        },

        cleanUrlTracking(rawUrl) {
            try {
                const url = new URL(rawUrl);
                let modified = false;

                for (const key of Config.TRACKERS) {
                    if (url.searchParams.has(key)) {
                        url.searchParams.delete(key);
                        modified = true;
                    }
                }

                return { modified, url: url.toString() };
            } catch {
                return { modified: false, url: rawUrl };
            }
        },

        isExternalHttpUrl(rawUrl) {
            try {
                const url = new URL(rawUrl, location.href);

                if (url.protocol !== 'http:' && url.protocol !== 'https:') {
                    return false;
                }

                const host = url.hostname.toLowerCase();

                return !(
                    host === 'telegram.org' ||
                    host.endsWith('.telegram.org') ||
                    host === 't.me' ||
                    host.endsWith('.t.me')
                );
            } catch {
                return false;
            }
        },

        openNewTab(url) {
            try {
                return window.open(
                    String(url || ''),
                    '_blank',
                    'noopener,noreferrer'
                );
            } catch (error) {
                Log.warn('OPEN_TAB', error);
                return null;
            }
        }
    };

    // =============================================================================
    // 05. GPU Safe 单实例 Toast
    // =============================================================================
    const UI = {
        toast: null,
        toastTimer: 0,

        initToast() {
            if (this.toast || !document.body) return;

            const toast = document.createElement('div');
            toast.id = 'telegram-ultimate-enhancer-toast';
            toast.setAttribute('role', 'status');
            toast.setAttribute('aria-live', 'polite');
            document.body.appendChild(toast);

            this.toast = toast;
        },

        showToast(message, duration = Config.TOAST_DURATION) {
            if (!document.body) return;

            this.initToast();
            if (!this.toast) return;

            clearTimeout(this.toastTimer);

            this.toast.textContent = String(message || '');
            this.toast.style.display = 'block';

            if (duration > 0) {
                this.toastTimer = setTimeout(() => {
                    if (this.toast) {
                        this.toast.style.display = 'none';
                    }
                }, duration);
            }
        }
    };

    // =============================================================================
    // 06. 使用反馈提示
    // =============================================================================
    const FeedbackPrompt = {
        STORAGE_KEY: 'telegram-ultimate-enhancer.feedback.v1',

        panel: null,
        eligibilityTimer: 0,
        testTimer: 0,
        passiveFeatureTimes: new Map(),

        defaultState() {
            return {
                firstSeenAt: Date.now(),
                lastSessionAt: 0,
                sessionCount: 0,
                successCount: 0,
                featureFlags: [],
                promptCount: 0,
                lastPromptAt: 0,
                actionClicked: false
            };
        },

        loadState() {
            const fallback = this.defaultState();

            try {
                const raw = localStorage.getItem(this.STORAGE_KEY);
                if (!raw) {
                    this.saveState(fallback);
                    return fallback;
                }

                const parsed = JSON.parse(raw);
                const state = {
                    ...fallback,
                    ...parsed
                };

                if (!Array.isArray(state.featureFlags)) {
                    state.featureFlags = [];
                }

                return state;
            } catch (error) {
                Log.warn('FEEDBACK_STORAGE_READ', error);
                return fallback;
            }
        },

        saveState(state) {
            try {
                localStorage.setItem(
                    this.STORAGE_KEY,
                    JSON.stringify(state)
                );
                return true;
            } catch (error) {
                Log.warn('FEEDBACK_STORAGE_WRITE', error);
                return false;
            }
        },

        init() {
            if (!Config.FEEDBACK_PROMPT_ENABLED || !document.body) return;

            if (Config.FEEDBACK_TEST_MODE) {
                clearTimeout(this.testTimer);

                this.testTimer = setTimeout(() => {
                    this.show({ testMode: true });
                }, Config.FEEDBACK_TEST_DELAY_MS);

                return;
            }

            this.loadState();
        },

        recordSuccess(feature, options = {}) {
            if (!Config.FEEDBACK_PROMPT_ENABLED) return;
            if (Config.FEEDBACK_TEST_MODE) return;

            const featureName = String(feature || '').trim();
            if (!featureName) return;

            const now = Date.now();
            const cooldownMs = Math.max(
                0,
                Number(options.cooldownMs || 0)
            );

            if (cooldownMs > 0) {
                const last = Number(
                    this.passiveFeatureTimes.get(featureName) || 0
                );

                if (now - last < cooldownMs) {
                    return;
                }

                this.passiveFeatureTimes.set(featureName, now);
            }

            const state = this.loadState();
            if (state.actionClicked) return;

            state.successCount =
                Math.max(0, Number(state.successCount || 0)) + 1;

            const features = new Set(state.featureFlags || []);
            features.add(featureName);
            state.featureFlags = Array.from(features);

            const lastSessionAt = Number(state.lastSessionAt || 0);
            if (
                !lastSessionAt ||
                now - lastSessionAt >= Config.FEEDBACK_SESSION_GAP_MS
            ) {
                state.sessionCount =
                    Math.max(0, Number(state.sessionCount || 0)) + 1;
                state.lastSessionAt = now;
            }

            this.saveState(state);
            this.scheduleIfEligible(state);
        },

        getCurrentStage(state) {
            const index = Math.max(
                0,
                Number(state.promptCount || 0)
            );

            return Config.FEEDBACK_STAGES[index] || null;
        },

        isEligible(state) {
            if (!state || state.actionClicked) return false;

            const stage = this.getCurrentStage(state);
            if (!stage) return false;

            const now = Date.now();
            const firstSeenAt = Number(state.firstSeenAt || now);
            const lastPromptAt = Number(state.lastPromptAt || 0);
            const featureCount = new Set(
                state.featureFlags || []
            ).size;

            if (now - firstSeenAt < stage.minAgeMs) return false;
            if (Number(state.sessionCount || 0) < stage.minSessions) {
                return false;
            }
            if (Number(state.successCount || 0) < stage.minSuccess) {
                return false;
            }
            if (featureCount < stage.minFeatures) return false;

            if (
                stage.minGapMs > 0 &&
                lastPromptAt > 0 &&
                now - lastPromptAt < stage.minGapMs
            ) {
                return false;
            }

            return true;
        },

        isSafeMoment() {
            if (!document.body) return false;
            if (document.hidden) return false;
            if (document.fullscreenElement) return false;

            const active = document.activeElement;
            if (
                active instanceof Element &&
                Utils.isEditableTarget(active)
            ) {
                return false;
            }

            return true;
        },

        scheduleIfEligible(state) {
            if (!this.isEligible(state)) return;
            if (this.panel || this.eligibilityTimer) return;

            this.eligibilityTimer = setTimeout(() => {
                this.eligibilityTimer = 0;

                const fresh = this.loadState();
                if (!this.isEligible(fresh)) return;
                if (!this.isSafeMoment()) return;

                this.show({ testMode: false });
            }, Config.FEEDBACK_IDLE_DELAY_MS);
        },

        removePanel() {
            if (this.panel?.isConnected) {
                this.panel.remove();
            }

            this.panel = null;
        },

        markActionClicked() {
            if (Config.FEEDBACK_TEST_MODE) return;

            const state = this.loadState();
            state.actionClicked = true;
            this.saveState(state);
        },

        show({ testMode = false } = {}) {
            if (!document.body || this.panel) return;

            if (!testMode) {
                const state = this.loadState();

                if (!this.isEligible(state)) {
                    return;
                }

                state.promptCount =
                    Math.max(0, Number(state.promptCount || 0)) + 1;
                state.lastPromptAt = Date.now();
                this.saveState(state);
            }

            const panel = document.createElement('section');
            panel.id = 'tue-feedback-prompt';
            panel.setAttribute('role', 'region');
            panel.setAttribute('aria-label', '支持项目持续维护');

            panel.innerHTML = `
                <div class="tue-feedback-head">
                    <div class="tue-feedback-title">
                        支持项目持续维护
                    </div>

                    <button
                        type="button"
                        class="tue-feedback-close"
                        aria-label="关闭本次提示"
                        title="关闭"
                    >×</button>
                </div>

                <div class="tue-feedback-copy">
                    <p>
                        如果 <strong>Telegram Ultimate Enhancer</strong>
                        对你的实际使用有所帮助,欢迎留下真实评价或为项目添加 GitHub Star。
                    </p>

                    <p>
                        <strong>
                            你的好评、Star 和推荐,是我持续更新、修复兼容问题和开发新功能的重要动力。
                        </strong>
                    </p>

                    <p>
                        长期缺少真实反馈和社区支持,也意味着项目的持续需求有限,
                        后续维护和新功能开发可能相应减少。
                    </p>
                </div>

                <div class="tue-feedback-actions">
                    <button
                        type="button"
                        class="tue-feedback-action tue-feedback-primary"
                        data-tue-feedback-action="greasyfork"
                    >
                        提交 Greasy Fork 评价
                    </button>

                    <button
                        type="button"
                        class="tue-feedback-action tue-feedback-secondary"
                        data-tue-feedback-action="github"
                    >
                        GitHub Star
                    </button>
                </div>
            `;

            const closeButton = panel.querySelector(
                '.tue-feedback-close'
            );

            closeButton?.addEventListener(
                'click',
                () => this.removePanel(),
                { once: true }
            );

            panel
                .querySelector(
                    '[data-tue-feedback-action="greasyfork"]'
                )
                ?.addEventListener(
                    'click',
                    () => {
                        this.markActionClicked();
                        Utils.openNewTab(Config.FEEDBACK_URL);
                        this.removePanel();
                    },
                    { once: true }
                );

            panel
                .querySelector(
                    '[data-tue-feedback-action="github"]'
                )
                ?.addEventListener(
                    'click',
                    () => {
                        this.markActionClicked();
                        Utils.openNewTab(Config.GITHUB_URL);
                        this.removePanel();
                    },
                    { once: true }
                );

            document.body.appendChild(panel);
            this.panel = panel;
        }
    };

    // =============================================================================
    // 07. Telegram 原生动作证据引擎
    // =============================================================================
    const NativeActionEvidence = Object.freeze({
        ACTION_TERMS: Object.freeze({
            download: Object.freeze(['download', '下载', 'save', '保存']),
            forward: Object.freeze(['forward', '转发'])
        }),

        GLYPH_HINTS: Object.freeze({
            '\ue977': 'download',
            '\ue995': 'forward'
        })
    });

    const ContextDetector = {
        CONTEXT_SELECTOR: [
            '.media-viewer-topbar',
            '.media-viewer-buttons',
            '#MediaViewer .MediaViewerActions',
            '#MediaViewer [class*="MediaViewerActions"]'
        ].join(','),

        isActionContext(element) {
            if (!(element instanceof Element)) return false;

            try {
                return element.matches(this.CONTEXT_SELECTOR);
            } catch {
                return false;
            }
        },

        closestActionContext(element) {
            if (!(element instanceof Element)) return null;

            try {
                if (this.isActionContext(element)) return element;
                return element.closest(this.CONTEXT_SELECTOR);
            } catch {
                return null;
            }
        },

        collectContexts(root) {
            if (!(root instanceof Element)) return [];

            const contexts = new Set();
            const direct = this.closestActionContext(root);
            if (direct) contexts.add(direct);

            try {
                for (
                    const context of root.querySelectorAll(
                        this.CONTEXT_SELECTOR
                    )
                ) {
                    contexts.add(context);
                }
            } catch {}

            return Array.from(contexts);
        }
    };

    const CandidateCollector = {
        CANDIDATE_SELECTOR: [
            'button',
            '[role="button"]',
            '.btn-icon',
            '.tgico-download',
            '.tgico-forward'
        ].join(','),

        collect(root) {
            if (!(root instanceof Element)) return [];

            const candidates = new Set();
            const contexts = ContextDetector.collectContexts(root);

            for (const context of contexts) {
                try {
                    if (context.matches(this.CANDIDATE_SELECTOR)) {
                        candidates.add(context);
                    }

                    for (
                        const element of context.querySelectorAll(
                            this.CANDIDATE_SELECTOR
                        )
                    ) {
                        candidates.add(element);
                    }
                } catch {}
            }

            return Array.from(candidates);
        }
    };

    const ActionClassifier = {
        normalize(value) {
            return String(value || '')
                .toLowerCase()
                .replace(/[_-]+/g, ' ')
                .replace(/\s+/g, ' ')
                .trim();
        },

        findAction(value) {
            const text = this.normalize(value);
            if (!text) return 'unknown';

            for (
                const [action, terms]
                of Object.entries(NativeActionEvidence.ACTION_TERMS)
            ) {
                if (terms.some(term => text.includes(term))) {
                    return action;
                }
            }

            return 'unknown';
        },

        classify(element) {
            const evidence = {
                action: 'unknown',
                direct: [],
                strong: [],
                weak: []
            };

            if (!(element instanceof Element)) return evidence;

            const actionVotes = [];
            const record = (
                bucket,
                source,
                value,
                action
            ) => {
                if (action === 'unknown') return;
                bucket.push(`${source}:${value}`);
                actionVotes.push(action);
            };

            const directSources = [
                ['aria-label', element.getAttribute('aria-label')],
                ['title', element.getAttribute('title')],
                ['data-action', element.getAttribute('data-action')],
                ['data-testid', element.getAttribute('data-testid')]
            ];

            for (const [source, value] of directSources) {
                const action = this.findAction(value);
                record(
                    evidence.direct,
                    source,
                    this.normalize(value),
                    action
                );
            }

            const className = this.normalize(element.className);
            const classAction = this.findAction(className);
            record(
                evidence.strong,
                'semantic-class',
                className,
                classAction
            );

            const context =
                ContextDetector.closestActionContext(element);
            if (context) {
                evidence.strong.push(
                    'telegram-media-action-context'
                );
            }

            if (
                element.matches?.(
                    'button,.btn-icon,[role="button"]'
                ) &&
                (
                    className.includes('btn icon') ||
                    className.includes('btn-icon') ||
                    element.tagName === 'BUTTON'
                )
            ) {
                evidence.strong.push(
                    'telegram-native-button-shape'
                );
            }

            const glyph =
                String(element.textContent || '').trim();
            const glyphAction =
                NativeActionEvidence.GLYPH_HINTS[glyph] ||
                'unknown';

            record(
                evidence.weak,
                'glyph-hint',
                glyph
                    ? `U+${glyph.codePointAt(0).toString(16)}`
                    : '',
                glyphAction
            );

            const uniqueVotes =
                Array.from(new Set(actionVotes));

            if (uniqueVotes.length === 1) {
                evidence.action = uniqueVotes[0];
            } else if (uniqueVotes.length > 1) {
                evidence.action = 'unknown';
                evidence.weak.push(
                    `conflict:${uniqueVotes.join('|')}`
                );
            }

            return evidence;
        }
    };

    const VisibilityInspector = {
        inspect(element) {
            const result = {
                hidden: false,
                reasons: []
            };

            if (!(element instanceof HTMLElement)) {
                return result;
            }

            if (element.classList.contains('hide')) {
                result.reasons.push('class:hide');
            }

            if (
                element.hidden ||
                element.hasAttribute('hidden')
            ) {
                result.reasons.push('attribute:hidden');
            }

            const inline = element.style;

            if (inline.display === 'none') {
                result.reasons.push('style:display-none');
            }
            if (inline.visibility === 'hidden') {
                result.reasons.push(
                    'style:visibility-hidden'
                );
            }
            if (inline.pointerEvents === 'none') {
                result.reasons.push(
                    'style:pointer-events-none'
                );
            }
            if (Number(inline.opacity || 1) <= 0) {
                result.reasons.push('style:opacity-zero');
            }

            result.hidden = result.reasons.length > 0;
            return result;
        }
    };

    const EvidenceGate = {
        allows(classification) {
            if (
                !classification ||
                classification.action === 'unknown'
            ) {
                return false;
            }

            const direct = classification.direct || [];
            const strong = classification.strong || [];

            return direct.length > 0 || strong.length >= 2;
        }
    };

    const ActionRestorer = {
        restore(element, classification, visibility) {
            if (!(element instanceof HTMLElement)) {
                return false;
            }
            if (!EvidenceGate.allows(classification)) {
                return false;
            }
            if (!visibility?.hidden) {
                return false;
            }

            let changed = false;

            if (element.classList.contains('hide')) {
                element.classList.remove('hide');
                changed = true;
            }

            if (element.hasAttribute('hidden')) {
                element.removeAttribute('hidden');
                changed = true;
            }

            if (element.style.display === 'none') {
                element.style.removeProperty('display');
                changed = true;
            }

            if (element.style.visibility === 'hidden') {
                element.style.removeProperty('visibility');
                changed = true;
            }

            if (element.style.pointerEvents === 'none') {
                element.style.removeProperty('pointer-events');
                changed = true;
            }

            if (Number(element.style.opacity || 1) <= 0) {
                element.style.removeProperty('opacity');
                changed = true;
            }

            if (changed) {
                element.classList.add(
                    'tue-native-action-restored'
                );
                element.dataset.tueAction =
                    classification.action;
                Cache.processedButtons.add(element);

                FeedbackPrompt.recordSuccess(
                    'native-action',
                    { cooldownMs: 60 * 1000 }
                );

                Log.info(
                    'NATIVE_ACTION',
                    `恢复 Telegram 原生 ${classification.action} 动作`,
                    {
                        direct: classification.direct,
                        strong: classification.strong,
                        weak: classification.weak,
                        hiddenReasons: visibility.reasons
                    }
                );
            }

            return changed;
        }
    };

    const NativeActionEngine = {
        mayContainCandidate(root) {
            if (!(root instanceof Element)) return false;

            try {
                if (
                    ContextDetector.closestActionContext(root)
                ) {
                    return true;
                }

                return Boolean(
                    root.querySelector(
                        ContextDetector.CONTEXT_SELECTOR
                    )
                );
            } catch {
                return false;
            }
        },

        processElement(element) {
            if (!(element instanceof HTMLElement)) {
                return false;
            }

            const visibility =
                VisibilityInspector.inspect(element);

            if (!visibility.hidden) return false;

            const classification =
                ActionClassifier.classify(element);

            return ActionRestorer.restore(
                element,
                classification,
                visibility
            );
        },

        processRoot(root) {
            if (!(root instanceof Element)) return 0;
            if (!this.mayContainCandidate(root)) return 0;

            let restored = 0;

            for (
                const candidate
                of CandidateCollector.collect(root)
            ) {
                if (this.processElement(candidate)) {
                    restored++;
                }
            }

            return restored;
        },

        initialScan() {
            if (!document.body) return 0;
            return this.processRoot(document.body);
        }
    };

    // =============================================================================
    // 08. 普通任务 DOM 调度器
    // =============================================================================
    const Scheduler = {
        pendingRoots: new Set(),
        flushTimer: 0,

        enqueue(root) {
            if (!(root instanceof Element)) return;

            if (!NativeActionEngine.mayContainCandidate(root)) {
                return;
            }

            for (const existing of this.pendingRoots) {
                if (
                    existing === root ||
                    existing.contains(root)
                ) {
                    return;
                }
            }

            for (
                const existing
                of Array.from(this.pendingRoots)
            ) {
                if (root.contains(existing)) {
                    this.pendingRoots.delete(existing);
                }
            }

            this.pendingRoots.add(root);

            if (
                this.pendingRoots.size >
                Config.DOM_QUEUE_SOFT_LIMIT
            ) {
                const oldest =
                    this.pendingRoots.values().next().value;

                if (oldest) {
                    this.pendingRoots.delete(oldest);
                }
            }

            this.schedule();
        },

        schedule() {
            if (this.flushTimer) return;

            this.flushTimer = setTimeout(
                () => this.flush(),
                Config.DOM_FLUSH_DELAY_MS
            );
        },

        flush() {
            this.flushTimer = 0;

            if (!this.pendingRoots.size) return;

            const roots = [];

            for (const root of this.pendingRoots) {
                roots.push(root);

                if (
                    roots.length >=
                    Config.DOM_MAX_ROOTS_PER_FLUSH
                ) {
                    break;
                }
            }

            for (const root of roots) {
                this.pendingRoots.delete(root);
                NativeActionEngine.processRoot(root);
            }

            if (this.pendingRoots.size) {
                this.schedule();
            }
        }
    };

    // =============================================================================
    // 09. 精准 MutationObserver
    // =============================================================================
    const ObserverEngine = {
        observer: null,

        start() {
            if (this.observer || !document.body) return;

            this.observer =
                new MutationObserver(mutations => {
                    for (const mutation of mutations) {
                        if (!mutation.addedNodes?.length) {
                            continue;
                        }

                        for (
                            const node
                            of mutation.addedNodes
                        ) {
                            if (node instanceof Element) {
                                Scheduler.enqueue(node);
                            }
                        }
                    }
                });

            this.observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            Log.info(
                'OBSERVER',
                'DOM 增量观察器已启动'
            );
        },

        stop() {
            if (!this.observer) return;

            this.observer.disconnect();
            this.observer = null;
        }
    };

    // =============================================================================
    // 10. 精准事件委托
    // =============================================================================
    const EventHub = {
        initialized: false,

        init() {
            if (this.initialized) return;
            this.initialized = true;

            this.setupCopy();
            this.setupContextMenu();
            this.setupKeyboard();
            this.setupClick();
        },

        setupKeyboard() {
            window.addEventListener(
                'keydown',
                event => {
                    if (
                        Utils.isEditableTarget(
                            event.target
                        )
                    ) {
                        return;
                    }

                    const key =
                        String(event.key || '')
                            .toLowerCase();

                    if (
                        key !== '[' &&
                        key !== ']' &&
                        key !== 'p'
                    ) {
                        return;
                    }

                    const video =
                        Utils.getActiveVideo();

                    if (!video) return;

                    if (key === ']') {
                        video.playbackRate =
                            Math.min(
                                4,
                                Number(
                                    video.playbackRate || 1
                                ) + 0.25
                            );

                        UI.showToast(
                            `倍速 ${video.playbackRate.toFixed(2)}×`
                        );

                        FeedbackPrompt.recordSuccess(
                            'speed'
                        );
                        return;
                    }

                    if (key === '[') {
                        video.playbackRate =
                            Math.max(
                                0.25,
                                Number(
                                    video.playbackRate || 1
                                ) - 0.25
                            );

                        UI.showToast(
                            `倍速 ${video.playbackRate.toFixed(2)}×`
                        );

                        FeedbackPrompt.recordSuccess(
                            'speed'
                        );
                        return;
                    }

                    if (key === 'p') {
                        if (
                            document.pictureInPictureElement
                        ) {
                            const result =
                                document
                                    .exitPictureInPicture?.();

                            if (
                                result &&
                                typeof result.then ===
                                    'function'
                            ) {
                                result
                                    .then(() => {
                                        FeedbackPrompt
                                            .recordSuccess(
                                                'pip'
                                            );
                                    })
                                    .catch(() =>
                                        UI.showToast(
                                            '退出画中画失败'
                                        )
                                    );
                            }
                        } else if (
                            typeof video
                                .requestPictureInPicture ===
                            'function'
                        ) {
                            video
                                .requestPictureInPicture()
                                .then(() => {
                                    FeedbackPrompt
                                        .recordSuccess(
                                            'pip'
                                        );
                                })
                                .catch(() =>
                                    UI.showToast(
                                        '画中画被浏览器拒绝'
                                    )
                                );
                        }
                    }
                },
                true
            );
        },

        setupCopy() {
            if (
                !Config.UNLOCK_PROTECTED_COPY &&
                !Config.CLEAN_TRACKING_PARAMS_ON_COPY
            ) {
                return;
            }

            window.addEventListener(
                'copy',
                event => {
                    if (
                        Utils.isEditableTarget(
                            event.target
                        )
                    ) {
                        return;
                    }

                    const selectedText =
                        Utils.selectionText();

                    if (!selectedText) return;

                    let outputText = selectedText;
                    let cleanedTracking = false;

                    if (
                        Config
                            .CLEAN_TRACKING_PARAMS_ON_COPY &&
                        /^https?:\/\/\S+$/i.test(
                            selectedText
                        )
                    ) {
                        const result =
                            Utils.cleanUrlTracking(
                                selectedText
                            );

                        if (result.modified) {
                            outputText = result.url;
                            cleanedTracking = true;
                        }
                    }

                    try {
                        if (event.clipboardData) {
                            event.clipboardData
                                .setData(
                                    'text/plain',
                                    outputText
                                );

                            event.preventDefault();
                            event.stopImmediatePropagation();

                            UI.showToast(
                                cleanedTracking
                                    ? '已复制,并清理链接跟踪参数'
                                    : '已复制选中文本'
                            );

                            FeedbackPrompt.recordSuccess(
                                cleanedTracking
                                    ? 'link-clean'
                                    : 'copy'
                            );
                            return;
                        }

                        if (
                            Config.UNLOCK_PROTECTED_COPY
                        ) {
                            event.stopImmediatePropagation();
                        }
                    } catch (error) {
                        Log.warn('COPY', error);
                    }
                },
                true
            );
        },

        setupContextMenu() {
            window.addEventListener(
                'contextmenu',
                event => {
                    const target = event.target;

                    if (!(target instanceof Element)) {
                        return;
                    }

                    if (
                        Config.UNLOCK_PROTECTED_COPY &&
                        Utils.selectionText()
                    ) {
                        event.stopImmediatePropagation();
                        return;
                    }

                    if (
                        target.closest(
                            'video,audio,img,.media-viewer,.media-container'
                        )
                    ) {
                        event.stopImmediatePropagation();
                    }
                },
                true
            );
        },

        setupClick() {
            window.addEventListener(
                'click',
                event => {
                    const target = event.target;

                    if (!(target instanceof Element)) {
                        return;
                    }

                    if (
                        Config.CONFIRM_CALL_ACTION
                    ) {
                        const callButton =
                            target.closest(
                                [
                                    '.tgico-phone',
                                    '.tgico-call',
                                    '[title*="Call" i]',
                                    '[title*="通话" i]',
                                    '[aria-label*="Call" i]'
                                ].join(',')
                            );

                        if (callButton) {
                            const confirmed =
                                window.confirm(
                                    '确定要发起语音/视频通话吗?'
                                );

                            if (!confirmed) {
                                event.preventDefault();
                                event.stopImmediatePropagation();

                                UI.showToast(
                                    '已取消通话操作'
                                );
                                return;
                            }
                        }
                    }

                    if (
                        !Config
                            .OPEN_EXTERNAL_LINKS_IN_NEW_TAB
                    ) {
                        return;
                    }

                    if (
                        event.button !== 0 ||
                        event.ctrlKey ||
                        event.shiftKey ||
                        event.altKey ||
                        event.metaKey
                    ) {
                        return;
                    }

                    const anchor =
                        target.closest('a[href]');

                    if (!anchor) return;
                    if (
                        anchor.hasAttribute('download')
                    ) {
                        return;
                    }
                    if (anchor.target === '_blank') {
                        return;
                    }

                    const href = anchor.href;

                    if (
                        !href ||
                        !Utils.isExternalHttpUrl(href)
                    ) {
                        return;
                    }

                    event.preventDefault();
                    event.stopPropagation();

                    Utils.openNewTab(href);
                    FeedbackPrompt.recordSuccess(
                        'external-link'
                    );
                },
                true
            );
        }
    };

    // =============================================================================
    // 11. 静态 CSS
    // =============================================================================
    function injectStaticStyle() {
        const rules = [];

        rules.push(`
            .message-text,
            .text-content,
            .translatable-message,
            .message .text,
            .bubble .text,
            .bubble-content .text,
            .is-protected,
            .restricted-content,
            .nocopy {
                -webkit-user-select: text !important;
                user-select: text !important;
            }

            .text-content,
            .message-text,
            .translatable-message {
                pointer-events: auto !important;
            }

            .is-protected::after,
            .is-protected::before,
            .nocopy::after,
            .nocopy::before {
                pointer-events: none !important;
            }
        `);

        if (Config.HIDE_SPONSORED_MESSAGES) {
            rules.push(`
                .sponsored-post,
                .sponsored-message,
                .channel-ad,
                [data-sponsored="true"] {
                    display: none !important;
                    pointer-events: none !important;
                }
            `);
        }

        if (Config.HIDE_PINNED_MESSAGES) {
            rules.push(`
                .pinned-messages-panel,
                .chat-pinned-message,
                .service-msg-pinned,
                .pinned-message {
                    display: none !important;
                    pointer-events: none !important;
                }
            `);
        }

        if (Config.UNLOCK_SPOILERS) {
            rules.push(`
                .spoiler,
                .spoiler-element,
                .text-spoiler {
                    filter: none !important;
                    background: transparent !important;
                    color: inherit !important;
                    opacity: 1 !important;
                }

                .media-spoiler,
                .media-spoiler-container,
                .spoiler-overlay {
                    display: none !important;
                    opacity: 0 !important;
                    pointer-events: none !important;
                }
            `);
        }

        rules.push(`
            .tue-native-action-restored {
                opacity: 1 !important;
                visibility: visible !important;
                pointer-events: auto !important;
            }
        `);

        rules.push(`
            #telegram-ultimate-enhancer-toast {
                position: fixed;
                top: 16px;
                right: 18px;
                z-index: 2147483000;

                display: none;
                box-sizing: border-box;
                max-width: min(78vw, 360px);
                min-height: 30px;
                padding: 7px 11px;

                border: 1px solid rgba(255,255,255,.14);
                border-radius: 9px;
                background: #202327;
                box-shadow: 0 4px 12px rgba(0,0,0,.20);

                color: #e7e9ee;
                font-family:
                    -apple-system,
                    BlinkMacSystemFont,
                    "Segoe UI",
                    "Microsoft YaHei",
                    sans-serif;
                font-size: 11px;
                font-weight: 650;
                line-height: 1.35;
                text-align: left;

                pointer-events: none;
                user-select: none;
            }
        `);

        rules.push(`
            #tue-feedback-prompt {
                position: fixed;
                right: 16px;
                bottom: 16px;
                z-index: 2147483001;

                box-sizing: border-box;
                width: min(328px, calc(100vw - 32px));
                padding: 14px;

                border: 1px solid #e4e4e7;
                border-radius: 10px;
                background: #ffffff;
                box-shadow: 0 8px 24px rgba(15, 23, 42, .10);

                color: #18181b;
                font-family:
                    -apple-system,
                    BlinkMacSystemFont,
                    "Segoe UI",
                    "Microsoft YaHei",
                    sans-serif;

                text-align: left;
            }

            #tue-feedback-prompt,
            #tue-feedback-prompt * {
                box-sizing: border-box;
            }

            #tue-feedback-prompt .tue-feedback-head {
                display: flex;
                align-items: flex-start;
                justify-content: space-between;
                gap: 10px;
                margin: 0 0 8px;
            }

            #tue-feedback-prompt .tue-feedback-title {
                min-width: 0;
                padding-top: 3px;

                color: #18181b;
                font-size: 15px;
                font-weight: 600;
                line-height: 1.3;
                letter-spacing: -.01em;
            }

            #tue-feedback-prompt .tue-feedback-close {
                flex: 0 0 auto;
                width: 28px;
                height: 28px;
                padding: 0;

                border: 1px solid transparent;
                border-radius: 6px;
                background: transparent;

                color: #71717a;
                font: inherit;
                font-size: 18px;
                font-weight: 400;
                line-height: 1;

                cursor: pointer;
            }

            #tue-feedback-prompt .tue-feedback-close:hover,
            #tue-feedback-prompt .tue-feedback-close:focus-visible {
                border-color: #e4e4e7;
                background: #f4f4f5;
                color: #18181b;
                outline: none;
            }

            #tue-feedback-prompt .tue-feedback-copy {
                color: #52525b;
                font-size: 12.5px;
                font-weight: 400;
                line-height: 1.55;
            }

            #tue-feedback-prompt .tue-feedback-copy p {
                margin: 0 0 8px;
            }

            #tue-feedback-prompt .tue-feedback-copy p:last-child {
                margin-bottom: 0;
            }

            #tue-feedback-prompt .tue-feedback-copy strong {
                color: #18181b;
                font-weight: 600;
            }

            #tue-feedback-prompt .tue-feedback-actions {
                display: grid;
                grid-template-columns: minmax(0, 1fr) 118px;
                gap: 8px;
                margin-top: 12px;
            }

            #tue-feedback-prompt .tue-feedback-action {
                min-width: 0;
                min-height: 36px;
                padding: 7px 10px;

                border-radius: 8px;

                font: inherit;
                font-size: 12.5px;
                font-weight: 500;
                line-height: 1.2;

                cursor: pointer;
            }

            #tue-feedback-prompt .tue-feedback-primary {
                border: 1px solid #18181b;
                background: #18181b;
                color: #fafafa;
            }

            #tue-feedback-prompt .tue-feedback-primary:hover,
            #tue-feedback-prompt .tue-feedback-primary:focus-visible {
                border-color: #27272a;
                background: #27272a;
                outline: none;
            }

            #tue-feedback-prompt .tue-feedback-secondary {
                border: 1px solid #d4d4d8;
                background: #ffffff;
                color: #18181b;
            }

            #tue-feedback-prompt .tue-feedback-secondary:hover,
            #tue-feedback-prompt .tue-feedback-secondary:focus-visible {
                border-color: #a1a1aa;
                background: #f4f4f5;
                outline: none;
            }

            @media (prefers-color-scheme: dark) {
                #tue-feedback-prompt {
                    border-color: #27272a;
                    background: #18181b;
                    box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
                    color: #fafafa;
                }

                #tue-feedback-prompt .tue-feedback-title,
                #tue-feedback-prompt .tue-feedback-copy strong {
                    color: #fafafa;
                }

                #tue-feedback-prompt .tue-feedback-copy {
                    color: #d4d4d8;
                }

                #tue-feedback-prompt .tue-feedback-close {
                    color: #a1a1aa;
                }

                #tue-feedback-prompt .tue-feedback-close:hover,
                #tue-feedback-prompt .tue-feedback-close:focus-visible {
                    border-color: #3f3f46;
                    background: #27272a;
                    color: #fafafa;
                }

                #tue-feedback-prompt .tue-feedback-primary {
                    border-color: #fafafa;
                    background: #fafafa;
                    color: #18181b;
                }

                #tue-feedback-prompt .tue-feedback-primary:hover,
                #tue-feedback-prompt .tue-feedback-primary:focus-visible {
                    border-color: #e4e4e7;
                    background: #e4e4e7;
                }

                #tue-feedback-prompt .tue-feedback-secondary {
                    border-color: #3f3f46;
                    background: #18181b;
                    color: #fafafa;
                }

                #tue-feedback-prompt .tue-feedback-secondary:hover,
                #tue-feedback-prompt .tue-feedback-secondary:focus-visible {
                    border-color: #52525b;
                    background: #27272a;
                }
            }

            @media (max-width: 420px) {
                #tue-feedback-prompt {
                    right: 10px;
                    bottom: 10px;
                    width: calc(100vw - 20px);
                }

                #tue-feedback-prompt .tue-feedback-actions {
                    grid-template-columns: 1fr;
                }
            }
        `);

        GM_addStyle(rules.join('\n'));
    }

    // =============================================================================
    // 12. 生命周期
    // =============================================================================
    const Lifecycle = {
        started: false,

        start() {
            if (this.started || !document.body) return;
            this.started = true;

            NativeActionEngine.initialScan();
            ObserverEngine.start();
            FeedbackPrompt.init();

            Log.info(
                'BOOT',
                'Evidence Native Actions / DOM增量 / 精准事件委托 / GPU Safe UI'
            );
        },

        boot() {
            injectStaticStyle();
            EventHub.init();

            if (document.body) {
                this.start();
                return;
            }

            document.addEventListener(
                'DOMContentLoaded',
                () => this.start(),
                { once: true }
            );
        }
    };

    Lifecycle.boot();
})();