携程航班信息提取器

支持所有主流浏览器的携程航班信息提取器,一键复制航班信息

// ==UserScript==
// @name         携程航班信息提取器
// @name:en      Ctrip Flight Info Extractor
// @namespace    https://greasyfork.org/users/[your-username]
// @version      1.2
// @description  支持所有主流浏览器的携程航班信息提取器,一键复制航班信息
// @description:en  Extract and copy flight information from Ctrip with one click
// @author       Senpou
// @license      MIT
// @match        https://flights.ctrip.com/online/list/*
// @match        http://flights.ctrip.com/online/list/*
// @match        https://m.ctrip.com/html5/flight/swift/domestic/*
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    GM_addStyle(`
        .flight-extractor-btn {
            position: fixed;
            top: 50%;
            right: 80px;
            transform: translateY(-50%);
            z-index: 9999;
            padding: 10px 20px;
            background-color: #2681ff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.1);
        }
        .flight-extractor-btn:hover {
            background-color: #1666d4;
            box-shadow: 0 4px 8px rgba(0,0,0,0.15);
        }
        .copy-flight-btn {
            margin: 5px 0;
            padding: 4px 6px;
            background-color: #2681ff;
            color: white;
            border: none;
            border-radius: 2px;
            cursor: pointer;
            font-size: 12px;
            width: auto;
            min-width: 40px;
            height: 24px;
            line-height: 16px;
            display: inline-block;
        }
        .copy-flight-btn:hover {
            background-color: #1666d4;
        }
    `);

    // 跨浏览器复制函数
    function copyToClipboard(text) {
        // 方法1: 使用 GM_setClipboard (Tampermonkey API)
        if (typeof GM_setClipboard !== 'undefined') {
            try {
                GM_setClipboard(text);
                return true;
            } catch (error) {
                console.error('GM_setClipboard 失败:', error);
            }
        }

        // 方法2: 使用 navigator.clipboard API
        if (navigator.clipboard && window.isSecureContext) {
            try {
                navigator.clipboard.writeText(text);
                return true;
            } catch (error) {
                console.error('Clipboard API 失败:', error);
            }
        }

        // 方法3: 传统的 execCommand 方法
        try {
            const textarea = document.createElement('textarea');
            textarea.value = text;
            // 确保在所有浏览器中都不可见
            textarea.style.cssText = 'position:fixed;pointer-events:none;z-index:-9999;opacity:0;';
            document.body.appendChild(textarea);

            // 适配移动设备
            if (navigator.userAgent.match(/ipad|iphone/i)) {
                textarea.contentEditable = true;
                textarea.readOnly = false;
                
                const range = document.createRange();
                range.selectNodeContents(textarea);
                
                const selection = window.getSelection();
                selection.removeAllRanges();
                selection.addRange(range);
                textarea.setSelectionRange(0, 999999);
            } else {
                textarea.select();
            }

            const successful = document.execCommand('copy');
            document.body.removeChild(textarea);
            return successful;
        } catch (error) {
            console.error('execCommand 失败:', error);
            return false;
        }
    }

    // 提取航班信息的主函数
    async function extractFlightInfo() {
        try {
            const flightCards = document.querySelectorAll('.flight-item');
            
            flightCards.forEach(card => {
                addCopyButton(card);
            });

        } catch (error) {
            console.error('提取航班信息时出错:', error);
        }
    }

    // 添加复制按钮到单个航班卡片
    function addCopyButton(card) {
        if (safeQuerySelector(card, '.copy-flight-btn')) {
            return;
        }

        try {
            const flightNoText = safeQuerySelector(card, '.plane-No')?.textContent.trim();
            const flightNo = flightNoText?.match(/^[A-Z0-9]+/)?.[0];
            const departTime = safeQuerySelector(card, '.depart-box .time')?.textContent.trim();
            const arriveTime = safeQuerySelector(card, '.arrive-box .time')?.textContent.trim()
                .replace(/\s*\+\d+天\s*/, '');
            const departAirport = safeQuerySelector(card, '.depart-box .airport')?.textContent.trim();
            const arriveAirport = safeQuerySelector(card, '.arrive-box .airport')?.textContent.trim();

            if (flightNo && departAirport && arriveAirport) {
                const info = `${flightNo} ${departTime}-${arriveTime} ${departAirport}-${arriveAirport}`;
                
                const priceArea = safeQuerySelector(card, '.flight-price');
                if (priceArea) {
                    const copyBtn = document.createElement('button');
                    copyBtn.className = 'copy-flight-btn';
                    copyBtn.textContent = '复制信息';
                    
                    // 使用 touchend 事件支持移动设备
                    const handleCopy = (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        
                        if (copyToClipboard(info)) {
                            copyBtn.textContent = '已复制';
                            setTimeout(() => {
                                copyBtn.textContent = '复制信息';
                            }, 1000);
                        } else {
                            copyBtn.textContent = '复制失败';
                            setTimeout(() => {
                                copyBtn.textContent = '复制信息';
                            }, 1000);
                        }
                    };

                    // 同时支持点击和触摸
                    copyBtn.addEventListener('click', handleCopy);
                    copyBtn.addEventListener('touchend', handleCopy);
                    
                    priceArea.insertAdjacentElement('beforebegin', copyBtn);
                }
            }
        } catch (error) {
            console.error('处理航班卡片时出错:', error);
        }
    }

    // 监听页面变化
    function observePageChanges() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) { // 元素节点
                        if (node.classList?.contains('flight-item')) {
                            addCopyButton(node);
                        }
                        // 检查子元素
                        const flightCards = node.querySelectorAll?.('.flight-item');
                        if (flightCards) {
                            flightCards.forEach(card => addCopyButton(card));
                        }
                    }
                });
            });
        });

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

    // 处理滚动事件
    function handleScroll() {
        extractFlightInfo();
    }

    // 添加兼容性检查和降级处理
    function checkBrowserCompatibility() {
        // 检查可选链操作符
        if (typeof window.MutationObserver === 'undefined') {
            console.warn('当前浏览器不支持 MutationObserver,将使用轮询方式');
            // 使用 setInterval 作为降级方案
            setInterval(extractFlightInfo, 2000);
            return false;
        }
        return true;
    }

    // 修改 initialize 函数
    function initialize() {
        // 添加兼容性检查
        const isModernBrowser = checkBrowserCompatibility();
        
        // 检查并记录可用的复制方法
        console.log('复制功能支持情况:', {
            'GM_setClipboard': typeof GM_setClipboard !== 'undefined',
            'Clipboard API': !!(navigator.clipboard && window.isSecureContext),
            'execCommand': typeof document.execCommand === 'function'
        });

        // 初始处理已有的航班卡片
        extractFlightInfo();
        
        // 根据浏览器支持情况选择监听方式
        if (isModernBrowser) {
            // 监听页面变化
            observePageChanges();
            // 添加滚动监听(使用 passive 选项提高性能)
            window.addEventListener('scroll', debounce(handleScroll, 200), { passive: true });
        }
    }

    // 防抖函数
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // 添加安全的选择器查询
    function safeQuerySelector(element, selector) {
        try {
            return element.querySelector(selector);
        } catch (error) {
            console.error('选择器查询失败:', error);
            return null;
        }
    }

    // 确保在 DOM 准备好后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
})();