EdgeDL

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         EdgeDL
// @namespace    https://github.com/Chumor/EdgeDL
// @version      2.2.0-dev.cc23a1a
// @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 finalOptions = typeof options === 'number' ? { duration: options } : options;
            const { duration = 900, type = 'info' } = finalOptions;
            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/', '/static/','/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,
        // 豌豆荚、九游
        /\/game\/downs_\d+_\d+\.html(?:[?#].*)?$/i,
        // 应用宝
        /\/\/sj\.qq\.com\/appdetail\/[^/?#]+(?:[?#].*)?$/i,
        /\/\/sj\.qq\.com\/myapp\/detail\.htm\?[^#]*(?:apkname|pkgname)=/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 downloadGestureUntil = 0;
    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);
        const inDownloadGesture = Date.now() <= downloadGestureUntil;
        if (!url || (!isDownloadLink(url) && !inDownloadGesture))
            return false;
        downloadGestureUntil = 0;
        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-ignore: 自定义属性拦截
        if (e._edgedl_handled)
            return;
        // @ts-ignore
        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]');
        if (downloadTrigger) {
            downloadGestureUntil = Date.now() + 1500;
        }
        const link = target?.closest?.('a, [onclick], [data-ng-href], [data-href], [data-url]');
        let url = '';
        if (link) {
            url = link.getAttribute('href')
                || link.getAttribute('data-ng-href')
                || link.getAttribute('data-href')
                || 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 { }
        });
    }

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

})();