Greasy Fork

Greasy Fork is available in English.

携程航班信息提取器

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         携程航班信息提取器
// @name:en      Ctrip Flight Info Extractor
// @namespace    http://greasyfork.icu/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();
    }
})();