EdgeDL

让 Android Edge 支持调用外部下载器接管下载任务

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         EdgeDL
// @namespace    https://github.com/Chumor/EdgeDL
// @version      2.2.0-dev.82f7650
// @description  让 Android Edge 支持调用外部下载器接管下载任务
// @icon         https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/edge.svg
// @author       Chumor
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_getResourceURL
// @grant        unsafeWindow
// @license      Apache-2.0
// @run-at       document-start
// @website      https://scriptcat.org/zh-CN/script-show-page/5391
// @supportURL   https://github.com/Chumor/EdgeDL/issues
// @resource     icon_idm       https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/idm.svg
// @resource     icon_idm_plus  https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/idm-plus.svg
// @resource     icon_adm       https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/adm.svg
// @resource     icon_abdm      https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/abdm.svg
// @resource     icon_fdm       https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/fdm.svg
// @resource     icon_edge      https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons/edge.svg
// ==/UserScript==


(function () {
    'use strict';

    const DOWNLOADERS={IDM: 'idm.internet.download.manager',IDM_PLUS: 'idm.internet.download.manager.plus',ADM: 'com.dv.adm',ABDM: 'com.abdownloadmanager',FDM: 'org.freedownloadmanager.fdm',};
    // 默认下载器
    const DEFAULT_DOWNLOADER_KEY = 'edgedl-default-downloader';
    // 版本信息
    function getEdgeDLVersion() {
        return (typeof GM_info !== 'undefined' && GM_info.script?.version)
            ? GM_info.script.version
            : 'dev';
    }

    const FALLBACK_ICON_BASE = 'https://cdn.jsdelivr.net/gh/Chumor/EdgeDL@main/assets/icons';
    function getIconUrl(name, fallback) {
        try {
            if (typeof GM_getResourceURL === 'function') {
                return GM_getResourceURL(name);
            }
        }
        catch {
            // ignore
        }
        return fallback;
    }
    const downloaderIcons = {
        IDM: getIconUrl('icon_idm', `${FALLBACK_ICON_BASE}/idm.svg`),
        IDM_PLUS: getIconUrl('icon_idm_plus', `${FALLBACK_ICON_BASE}/idm-plus.svg`),
        ADM: getIconUrl('icon_adm', `${FALLBACK_ICON_BASE}/adm.svg`),
        ABDM: getIconUrl('icon_abdm', `${FALLBACK_ICON_BASE}/abdm.svg`),
        FDM: getIconUrl('icon_fdm', `${FALLBACK_ICON_BASE}/fdm.svg`),
        EDGE: getIconUrl('icon_edge', `${FALLBACK_ICON_BASE}/edge.svg`),
    };

    async function showDownloadPicker(callback) {
        if (document.getElementById('edgedl-picker')) {
            callback(null);
            return;
        }
        const picker = document.createElement('div');
        picker.id = 'edgedl-picker';
        picker.classList.add('initializing');
        const shadow = picker.attachShadow({ mode: 'open' });
        shadow.innerHTML = `<div class="edgedl-bg"></div><div class="edgedl-card"><h3>选择下载器</h3><div class="edgedl-version-tag">EdgeDL v${getEdgeDLVersion()}</div><div class="edgedl-options"><button data-pkg="${DOWNLOADERS.IDM}"><img src="${downloaderIcons.IDM}" />1DM </button><button data-pkg="${DOWNLOADERS.IDM_PLUS}"><img src="${downloaderIcons.IDM_PLUS}" />1DM+ </button><button data-pkg="${DOWNLOADERS.ADM}"><img src="${downloaderIcons.ADM}" />ADM </button><button data-pkg="${DOWNLOADERS.ABDM}"><img src="${downloaderIcons.ABDM}" />ABDM </button><button data-pkg="${DOWNLOADERS.FDM}"><img src="${downloaderIcons.FDM}" />FDM </button><button data-pkg="edge"><img src="${downloaderIcons.EDGE}" />Edge </button></div><label style="margin-top:12px;display:flex;align-items:center;gap:6px;font-size:13px;"><input type="checkbox" id="edgedl-set-default" />设为默认下载器 </label></div>`;
        document.documentElement.appendChild(picker);
        const layoutPicker = () => {
            const vvp = window.visualViewport;
            const w = vvp ? vvp.width : document.documentElement.clientWidth;
            const card = shadow.querySelector('.edgedl-card');
            if (card)
                card.style.maxWidth = (w - 32) + 'px';
        };
        layoutPicker();
        if (window.visualViewport) {
            window.visualViewport.addEventListener('resize', layoutPicker);
            window.visualViewport.addEventListener('scroll', layoutPicker);
        }
        const style = document.createElement('style');
        style.textContent = `:host{all:initial;position:fixed;inset:0;display:flex;justify-content:center;align-items:center;z-index:2147483647;pointer-events:none;contain:layout style paint;isolation:isolate;--edgedl-move-easing:cubic-bezier(0,0,0.2,1);--edgedl-fade-easing:cubic-bezier(0.4,0,0.2,1);--edgedl-exit-easing:cubic-bezier(0.4,0,1,1);--edgedl-enter-duration:300ms;--edgedl-fade-duration:200ms;--edgedl-exit-duration:200ms;}*,*::before,*::after{box-sizing:border-box;}.edgedl-bg{position:absolute;inset:0;background:rgba(0,0,0,0.42);backdrop-filter:blur(25px) saturate(140%);-webkit-backdrop-filter:blur(25px) saturate(140%);animation:edgedl-fade-in var(--edgedl-fade-duration) var(--edgedl-fade-easing) both;pointer-events:auto;will-change:opacity;}:host(.initializing) .edgedl-bg,:host(.initializing) .edgedl-card{pointer-events:none;user-select:none;cursor:wait;}.edgedl-card{position:relative;background:#fff;border-radius:24px;padding:20px;width:260px;max-width:100%;box-shadow:0 10px 28px rgba(0,0,0,0.25);display:flex;flex-direction:column;align-items:center;animation:edgedl-slide-up var(--edgedl-enter-duration) var(--edgedl-move-easing) both;transform-origin:center bottom;will-change:opacity,transform;pointer-events:auto;box-sizing:border-box;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.4;-webkit-font-smoothing:antialiased;}h3{margin:8px 0 18px 0;font-weight:600;font-size:16px;color:#333;}.edgedl-version-tag{position:absolute;top:10px;right:12px;font-size:9px;transform:translate(0,0);font-family:ui-monospace,SFMono-Regular,monospace;color:#888;background:rgba(0,0,0,0.04);padding:2px 8px;border-radius:12px;font-weight:600;letter-spacing:0.3px;pointer-events:none;border:1px solid rgba(0,0,0,0.02);}.edgedl-options{display:flex;flex-direction:column;width:100%;gap:12px;}.edgedl-options button{display:flex;align-items:center;gap:10px;padding:10px;border:none;border-radius:12px;background:#F0F0F0;font-weight:500;cursor:pointer;transition:background 0.2s;}.edgedl-options button:hover{background:#e0e0e0;}.edgedl-options img{width:24px;height:24px;}.edgedl-options button.selected{background:rgba(76,175,80,0.12);outline:1.5px solid rgba(76,175,80,0.72);}@media (prefers-color-scheme:dark){.edgedl-card{background:#292929;color:#FFFFFF;box-shadow:0 4px 16px rgba(0,0,0,0.6);}h3{color:#F5F5F5;}.edgedl-options button{background:#383838;color:#FFFFFF;}#edgedl-picker .edgedl-options button.selected{background:rgba(129,199,132,0.16);outline-color:rgba(129,199,132,0.82);}}@keyframes edgedl-fade-in{from{opacity:0;}to{opacity:1;}}@keyframes edgedl-slide-up{from{opacity:0;transform:translateY(24px) scale(0.96);}to{opacity:1;transform:translateY(0) scale(1);}}@keyframes edgedl-fade-out{from{opacity:1;}to{opacity:0;}}@keyframes edgedl-slide-down{from{opacity:1;transform:translateY(0) scale(1);}to{opacity:0;transform:translateY(12px) scale(0.98);}}:host(.closing) .edgedl-bg{animation:edgedl-fade-out var(--edgedl-exit-duration) var(--edgedl-exit-easing) forwards;}:host(.closing) .edgedl-card{animation:edgedl-slide-down var(--edgedl-exit-duration) var(--edgedl-exit-easing) forwards;}@media (prefers-reduced-motion:reduce){.edgedl-bg,.edgedl-card,:host(.closing) .edgedl-bg,:host(.closing) .edgedl-card{animation-duration:1ms;}}`;
        shadow.appendChild(style);
        // 读取默认下载器
        const defaultDownloader = await GM_getValue(DEFAULT_DOWNLOADER_KEY, null);
        const defaultCheckbox = shadow.querySelector('#edgedl-set-default');
        if (defaultCheckbox)
            defaultCheckbox.checked = !!defaultDownloader;
        if (defaultDownloader) {
            // 高亮默认下载器按钮
            const defaultBtn = shadow.querySelector(`button[data-pkg="${defaultDownloader}"]`);
            if (defaultBtn)
                defaultBtn.classList.add('selected');
        }
        // 取消勾选时清除默认下载器
        defaultCheckbox.addEventListener('change', async () => {
            if (!defaultCheckbox.checked) {
                await GM_deleteValue(DEFAULT_DOWNLOADER_KEY);
                shadow.querySelector('button.selected')?.classList.remove('selected');
            }
        });
        // 点击唤起
        shadow.querySelectorAll('button').forEach(btn => {
            btn.addEventListener('click', async () => {
                const pkg = btn.dataset.pkg || '';
                // 按当前选择更新默认下载器
                if (pkg === 'edge') {
                    await GM_deleteValue(DEFAULT_DOWNLOADER_KEY);
                }
                else if (defaultCheckbox.checked) {
                    await GM_setValue(DEFAULT_DOWNLOADER_KEY, pkg);
                }
                callback(pkg);
                gotoClose(false);
            });
        });
        picker.classList.remove('initializing');
        function gotoClose(cancelled = true) {
            if (picker.classList.contains('closing'))
                return;
            if (cancelled)
                callback(null);
            picker.classList.add('closing');
            let removed = false;
            const removePicker = () => {
                if (removed)
                    return;
                removed = true;
                if (window.visualViewport) {
                    window.visualViewport.removeEventListener('resize', layoutPicker);
                    window.visualViewport.removeEventListener('scroll', layoutPicker);
                }
                picker.remove();
                window.dispatchEvent(new CustomEvent('edgedl:picker-closed'));
            };
            const card = shadow.querySelector('.edgedl-card');
            const onAnimationEnd = (event) => {
                if (event.target !== card)
                    return;
                card.removeEventListener('animationend', onAnimationEnd);
                removePicker();
            };
            card?.addEventListener('animationend', onAnimationEnd);
            window.setTimeout(removePicker, 260);
        }
        shadow.querySelector('.edgedl-bg')?.addEventListener('click', () => gotoClose());
    }

    function buildIntentUrl(url, packageName) {
        const scheme = url.startsWith('https') ? 'https' : 'http';
        const path = url.replace(/^https?:\/\//, '');
        return `intent://${path}#Intent;scheme=${scheme};package=${packageName};type=*/*;action=android.intent.action.VIEW;category=android.intent.category.BROWSABLE;end`;
    }

    function openDownloader(url, type) {
        if (!url || typeof url !== 'string') {
            throw new Error('Invalid URL');
        }
        const packageName = DOWNLOADERS[type];
        if (!packageName) {
            throw new Error(`Unknown downloader type:${type}`);
        }
        const intentUrl = buildIntentUrl(url, packageName);
        window.location.href = intentUrl;
    }

    let styleInjected = false;
    let activeToast = null;
    // 注入全局样式
    function injectStyle() {
        if (styleInjected)
            return;
        styleInjected = true;
        const style = document.createElement('style');
        style.textContent = `.edgedl-toast{position:fixed;top:64px;right:12px;left:auto;bottom:auto;transform:translateX(20px);z-index:999999;pointer-events:none;font-size:13px;font-weight:500;line-height:1.4;padding:7px 14px;border-radius:10px;white-space:nowrap;opacity:0;transition:opacity .22s ease,transform .22s ease;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);}.edgedl-toast[data-theme="light"]{background:#333333;color:#fff;box-shadow:0 4px 10px rgba(0,0,0,.25);}.edgedl-toast[data-theme="dark"]{background:#383838;color:#f5f5f5;border:1px solid rgba(255,255,255,.08);box-shadow:0 6px 16px rgba(0,0,0,.6);}.edgedl-toast.show{opacity:1;transform:translateX(0);}.edgedl-toast[data-type="error"]::before{content:"⚠";margin-right:6px;color:#f87171;}.edgedl-toast[data-type="info"]::before{content:"ⓘ";margin-right:6px;opacity:.8;}`;
        document.head.appendChild(style);
    }
    function getTheme() {
        return window.matchMedia &&
            window.matchMedia('(prefers-color-scheme: dark)').matches
            ? 'dark'
            : 'light';
    }
    function showToast(message, options = {}) {
        try {
            injectStyle();
            const { duration = 900, type = 'info' } = options;
            if (activeToast)
                activeToast.remove();
            const toast = document.createElement('div');
            toast.className = 'edgedl-toast';
            toast.dataset.theme = getTheme();
            toast.dataset.type = type;
            toast.textContent = message;
            document.body.appendChild(toast);
            activeToast = toast;
            requestAnimationFrame(() => {
                toast.classList.add('show');
            });
            setTimeout(() => {
                toast.classList.remove('show');
                toast.addEventListener('transitionend', () => {
                    toast.remove();
                    if (activeToast === toast)
                        activeToast = null;
                }, { once: true });
            }, duration);
        }
        catch (err) {
            console.warn('Toast 创建失败', err);
        }
    }

    async function openDownload(url, downloader) {
        const launcherKey = Object.keys(DOWNLOADERS)
            .find((key) => DOWNLOADERS[key] === downloader);
        if (launcherKey) {
            showToast(`${launcherKey} 正在唤起`);
            openDownloader(url, launcherKey);
            return;
        }
        showToast('Edge 内置下载');
        setTimeout(() => {
            window.location.href = url;
        }, 0);
    }

    async function requestDownload(url) {
        if (!url)
            return;
        const dl = await GM_getValue(DEFAULT_DOWNLOADER_KEY);
        if (dl) {
            return openDownload(url, dl);
        }
        const selected = await new Promise((resolve) => {
            showDownloadPicker(resolve);
        });
        if (selected) {
            return openDownload(url, selected);
        }
        return null;
    }

    // 下载文件后缀匹配
    const EXTENSIONS=['.apk', '.apks', '.xapk', '.apkm', '.ipa', '.obb', '.aab','.zip', '.rar', '.7z', '.tar', '.gz', '.tgz', '.bz2', '.xz','.iso', '.cab', '.jar', '.z','.mp4', '.mkv', '.avi', '.mov', '.flv', '.wmv', '.webm','.m4v', '.3gp', '.ts', '.mpg', '.mpeg', '.vob','.mp3', '.flac', '.wav', '.ogg', '.m4a', '.aac', '.wma', '.ape','.pdf', '.epub', '.mobi', '.azw3', '.djvu','.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx','.exe', '.msi', '.bin', '.dat', '.dmg', '.bat', '.sh', '.img','.torrent'];
    // 下载链接特征匹配
    const KEYWORDS=['/down/', '/download/', '/downloads/', '/dl/', '/fetch/','/files/', '/file/', '/attach/', '/attachment/', '/media/','/assets/', '/cdn/', '/dist/', '/repo/', '/backup/', '/upload/','/releases/download/', '/binary/', '/pkg/','?file=', '&file=', '?filename=', '&filename=','download?', '&download=', '?download=','force_download', 'response-content-disposition=', 'content-disposition=attachment'];
    // 非下载页面排除规则
    const EXCLUDE_PATHS = /\/(login|reg(ister)?|sign(in|up|out)|logout|account|user|blob|src|tree)(?:[\/?#]|$)/i;
    // 下载跳转页匹配
    const REDIRECT_DOWNLOAD_PAGES = [
        // 腾讯游戏
        /\/zlkdatasys\/mct\/(?:d\/[^/?#]+|proj_\d+\/download)\.shtml(?:[?#].*)?$/i,
    ];
    // 下载链接检测
    function isDownloadLink(url) {
        if (url?.includes('sourceforge.net/projects/') && url.includes('/files/'))
            return false;
        if (!url || !url.startsWith('http'))
            return false;
        const lowerUrl = url.toLowerCase();
        // 排除非下载页面
        if (EXCLUDE_PATHS.test(lowerUrl))
            return false;
        // 排除类 Unix 目录路径
        if (/\/data\/data\/[^?#]*$/i.test(lowerUrl))
            return false;
        // 下载跳转页匹配
        if (REDIRECT_DOWNLOAD_PAGES.some(pattern => pattern.test(lowerUrl)))
            return true;
        // 后缀匹配
        try {
            const path = new URL(url).pathname.toLowerCase();
            if (EXTENSIONS.some(ext => path.endsWith(ext)))
                return true;
        }
        catch {
            const path = lowerUrl.split('?')[0].split('#')[0];
            if (EXTENSIONS.some(ext => path.endsWith(ext)))
                return true;
        }
        // 关键字匹配
        return KEYWORDS.some(kw => lowerUrl.includes(kw));
    }

    function extractUrlFromOnclick(onclick) {
        if (!onclick)
            return null;
        const URL_PATTERN = /(https?:\/\/[^"'()\s]+)/i;
        const match = onclick.match(URL_PATTERN);
        if (match) {
            return match[1];
        }
        return null;
    }

    let interceptEnabled = true;
    let bridgeAttached = false;
    function getPageWindow() {
        return (globalThis.unsafeWindow || window);
    }
    function isInvalidNavigationUrl(url) {
        const value = url.trim().toLowerCase();
        return !value || value === '#' || value === '##' || value.startsWith('javascript:');
    }
    function normalizeUrl(input) {
        if (!input)
            return '';
        try {
            const url = new URL(String(input), location.href).href;
            return url.startsWith('http') ? url : '';
        }
        catch {
            return '';
        }
    }
    function tryInterceptNavigation(input) {
        if (!interceptEnabled)
            return false;
        const url = normalizeUrl(input);
        if (!url || !isDownloadLink(url))
            return false;
        void requestDownload(url);
        return true;
    }
    const KEY = 'edgedl-site-intercept';
    // 获取接管站点列表
    async function getInterceptSites() {
        const list = await GM_getValue(KEY, []);
        return Array.isArray(list) ? list : [];
    }
    // 保存接管站点列表
    async function setInterceptSites(list) {
        const normalized = list.map((i) => typeof i === 'string' ? i.toLowerCase() : String(i).toLowerCase());
        await GM_setValue(KEY, normalized);
        interceptEnabled = !normalized.includes(location.hostname.toLowerCase());
        return normalized;
    }
    // 判断当前站点是否已跳过接管
    async function isSiteIntercepted() {
        const host = location.hostname.toLowerCase();
        const list = await getInterceptSites();
        if (list.length === 0)
            return false;
        return list.some(item => item.toLowerCase() === host);
    }
    // 切换当前站点接管状态
    async function toggleSiteIntercept() {
        const host = location.hostname.toLowerCase();
        const list = await getInterceptSites();
        let added;
        const index = list.findIndex(item => item.toLowerCase() === host);
        if (index >= 0) {
            list.splice(index, 1);
            added = false;
        }
        else {
            list.push(host);
            added = true;
        }
        await setInterceptSites(list);
        return added;
    }
    // 挂载点击拦截器
    function attachClickInterceptor() {
        document.addEventListener('click', handleClick, true);
    }
    function handleClick(e) {
        // @ts-expect-error: 自定义属性拦截
        if (e._edgedl_handled)
            return;
        // @ts-expect-error
        e._edgedl_handled = true;
        const target = e.target;
        if (target?.closest?.('label.hope-checkbox, .hope-checkbox, .hope-checkbox__control, input[type="checkbox"]'))
            return;
        const downloadTrigger = target?.closest?.('[class*="download" i], [id*="download" i], [dt-eid*="download" i]');
        const link = target?.closest?.('a, [onclick], [data-ng-href], [data-href], [data-url], [data-gokey]');
        let url = '';
        if (link) {
            url = link.getAttribute('href')
                || link.getAttribute('data-ng-href')
                || link.getAttribute('data-href')
                || (link.getAttribute('data-gokey')?.match(/download_url=([^&]+)/)?.[1])
                || link.getAttribute('data-url')
                || link.href
                || '';
        }
        if (isInvalidNavigationUrl(url)) {
            const onclick = link
                ? link.getAttribute('onclick') || link.closest('[onclick]')?.getAttribute('onclick')
                : target?.closest?.('[onclick]')?.getAttribute('onclick');
            if (onclick) {
                url = extractUrlFromOnclick(onclick) || '';
            }
        }
        if (isInvalidNavigationUrl(url) &&
            downloadTrigger &&
            isDownloadLink(location.href)) {
            url = location.href;
        }
        else {
            url = normalizeUrl(url);
        }
        if (!url || !isDownloadLink(url))
            return;
        // 命中接管排除策略:跳过 EdgeDL 接管并提示,交还浏览器默认行为
        if (!interceptEnabled) {
            showToast('已跳过接管', { type: 'info', duration: 1500 });
            return;
        }
        // 阻止浏览器原生下载与页面跳转
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        void requestDownload(url);
    }
    // 接管页面脚本触发的下载跳转
    function attachPageBridgeInterceptor() {
        if (bridgeAttached)
            return;
        bridgeAttached = true;
        attachClickInterceptor();
        void isSiteIntercepted().then((value) => {
            interceptEnabled = !value;
        });
        const pageWindow = getPageWindow();
        const originalOpen = pageWindow.open;
        pageWindow.open = function patchedOpen(url, target, features) {
            if (tryInterceptNavigation(url))
                return null;
            return originalOpen.call(pageWindow, url, target, features);
        };
        try {
            const originalClick = pageWindow.HTMLAnchorElement.prototype.click;
            pageWindow.HTMLAnchorElement.prototype.click = function patchedClick() {
                if (tryInterceptNavigation(this.href))
                    return;
                return originalClick.call(this);
            };
        }
        catch { }
        ['assign', 'replace'].forEach((method) => {
            try {
                const original = pageWindow.Location.prototype[method];
                pageWindow.Location.prototype[method] =
                    function patchedLocation(url) {
                        if (tryInterceptNavigation(url))
                            return;
                        return original.call(this, url);
                    };
            }
            catch { }
        });
        // 拦截通过隐藏 <iframe> 动态唤起的下载行为
        try {
            const desc = Object.getOwnPropertyDescriptor(pageWindow.HTMLIFrameElement.prototype, 'src');
            if (desc && desc.set) {
                const originalSet = desc.set;
                Object.defineProperty(pageWindow.HTMLIFrameElement.prototype, 'src', {
                    ...desc,
                    set: function patchedIframeSrc(value) {
                        if (value && isDownloadLink(value) && tryInterceptNavigation(value))
                            return;
                        originalSet.call(this, value);
                    }
                });
            }
            const originalSetAttribute = pageWindow.Element.prototype.setAttribute;
            pageWindow.Element.prototype.setAttribute = function patchedSetAttribute(name, value) {
                if (this instanceof pageWindow.HTMLIFrameElement && name.toLowerCase() === 'src') {
                    if (value && isDownloadLink(value) && tryInterceptNavigation(value))
                        return;
                }
                return originalSetAttribute.call(this, name, value);
            };
        }
        catch { }
    }

    let menuRegistered = false;
    function registerMenu() {
        if (menuRegistered)
            return;
        if (typeof GM_registerMenuCommand !== 'function')
            return;
        // 更改默认下载器
        GM_registerMenuCommand('更改默认下载器', () => {
            showDownloadPicker(() => { });
        });
        // 切换站点接管状态
        GM_registerMenuCommand('切换本站接管状态', async () => {
            const added = await toggleSiteIntercept();
            showToast(added ? '已禁止接管本站' : '已允许接管本站', { type: 'info', duration: 1500 });
        });
        menuRegistered = true;
    }

    function initDeepSeekHandler() {
        if (!location.hostname.includes('download.deepseek.com'))
            return;
        const APK_URL = "https://download.deepseek.com/apk/deepseek.apk";
        const takeover = (e) => {
            if (!e || e.defaultPrevented)
                return;
            e.preventDefault?.();
            e.stopPropagation?.();
            e.stopImmediatePropagation?.();
            requestDownload(APK_URL);
            return false;
        };
        document.addEventListener('click', e => {
            const target = e.target;
            if (target?.closest?.('div')?.textContent?.includes('下载 APK 文件'))
                takeover(e);
        }, true);
    }

    // 初始化下载接管
    function init() {
        attachPageBridgeInterceptor();
        registerMenu();
        initDeepSeekHandler();
    }
    init();

})();