國泰獎勵機票搜尋引擎修復神器 2022

國泰航空 2022 里程獎勵機票搜尋引擎「反升級」套件

目前為 2022-08-28 提交的版本,檢視 最新版本

// ==UserScript==
// @name                Cathay Award Search Fixer 2022
// @name:zh-TW          國泰獎勵機票搜尋引擎修復神器 2022
// @namespace           jayliutw
// @version             2.0
// @description         Un-Elevate Your Cathay Award Search 2022
// @description:zh-TW   國泰航空 2022 里程獎勵機票搜尋引擎「反升級」套件
// @author              jayliutw
// @match               https://*.cathaypacific.com/cx/*/book-a-trip/redeem-flights/redeem-flight-awards.html*
// @match               https://book.cathaypacific.com/*
// @grant               GM_setValue
// @grant               GM_getValue
// @license             GPL
// ==/UserScript==

(function() {
    'use strict';

    function addCss(cssString) {
        var head = document.getElementsByTagName('head')[0];
        var newCss = document.createElement('style');
        newCss.type = "text/css";
        newCss.innerHTML = cssString;
        head.appendChild(newCss);
    }


    var uef_from = GM_getValue("uef_from", "HKG") || localStorage.getItem("uef_from") || "HKG";
    var uef_to = GM_getValue("uef_to", "TYO") || localStorage.getItem("uef_to") || "TYO";
    var uef_date = GM_getValue("uef_date", "20230801") || localStorage.getItem("uef_date") || "20230801";
    var uef_adult = GM_getValue("uef_adult", "1") || localStorage.getItem("uef_adult") || "1";
    var uef_child = GM_getValue("uef_child", "0") || localStorage.getItem("uef_child") || "0";

    addCss (
        '.unelevated_form { position:relative;transition: margin-left 0.7s ease-out;z-index: 21; font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Rg, sans-serif; border: 1px solid #bcbec0; margin:10px 0; background: #f7f6f0; padding: 8px 0px 8px 8px; border-top: 5px solid #367778; box-shadow: 0px 0px 7px rgb(0 0 0 / 20%);} ' +
        '.unelevated_form.uef_collapsed { margin-left:-90%;} ' +
        '.unelevated_title {font-weight: 400; font-size: 17px; font-family: "GT Walsheim","Cathay Sans EN", CathaySans_Lt, sans-serif; color: #2d2d2d; margin: 5px;} '+
        '.unelevated_form label { display: inline-block; position: relative; width:50%; padding: 0px 8px 0px 0px; } ' +
        '.unelevated_form label span { position: absolute; top: 0px; left: 5px; color: #66686a; font-family: Cathay Sans EN, CathaySans_Rg, sans-serif; line-height: 25px; font-size: 10px;} ' +
        '.unelevated_form input { font-family: Cathay Sans EN, CathaySans_Lt, sans-serif; padding: 19px 5px 5px 5px; border-radius: 0px; border: 1px solid #bcbec0; display: inline-block; margin: 0px 8px 8px 0px; height: 45px; width: 100%;} ' +
        '.unelevated_form button.uef_search { background-color: #367778; border: none; color: white; display: inline-block;vertical-align: top; margin: 0px; height: 45px; width: calc(50% - 8px);} ' +
        '.unelevated_sub a { font-family: Cathay Sans EN, CathaySans_Bd, sans-serif; font-size: 15px !important; text-decoration:underline !important; margin: 0px; color: #0f748f; font-weight: bold;}' +
        'a.uef_toggle, a.uef_toggle:hover { background: #367778; display: block; position: absolute; right: -1px; top: -5px; padding-top:5px; width: 30px; text-align: center; text-decoration: none; color: white !important; padding-bottom: 5px; }' +
        'a.uef_toggle:after {content:\'«\'} .uef_collapsed a.uef_toggle:after {content : \'»\'} ' +
        '.bulk_hidden {display:none;}' +
        '.bulk_box {min-height: 60px; transition: margin-top 0.7s ease-out;background: #f7f6f0; padding: 8px; border: 1px solid #bcbec0; box-shadow: 0px 0px 7px rgb(0 0 0 / 20%); margin-top: -11px; margin-bottom: 20px; z-index: 20; position: relative;}' +
        '.bulk_box_hidden {position:relative; margin-top:-80px;}' +
        '.bulk_table { width:100%; border: 1px solid #c6c2c1; margin: 10px 0; font-size: 12px;}' +
        '.bulk_table th { text-align:center; font-weight:bold; background: #ebedec; }' +
        '.bulk_table td { background:white; }' +
        '.bulk_table tr:nth-child(even) td { background:#f9f9f9; }' +
        '.bulk_table th, .bulk_table td { border: 1px solid #c6c2c1; padding: 5px; }' +
        '.bulk_table .bulk_date { width:100px; text-align:center; }' +
        '.bulk_table .bulk_date a { text-decoration:underline !important; }' +
        '.bulk_table td.bulk_flights { padding:5px 5px 0 5px }' +
        '.bulk_table .bulk_flights .bulk_no_flights { display:block;padding-bottom:5px; }' +
        '.bulk_table .flight_item { display: inline-block; background: #e0e0e0; line-height:15px; padding: 6px; border-radius: 5px; margin-right: 10px; margin-bottom: 5px; white-space: nowrap; }' +
        '.bulk_table .flight_item img { line-height: 15px; max-height: 15px; vertical-align: middle; margin-right: 5px; max-width: 20px;}' +
        '.bulk_table .flight_item.direct { background: #cbe0cf; }' +
        '.bulk_table .flight_item span { padding: 2px 5px; color: white; border-radius: 5px; margin-left: 5px; }' +
        'span.bulk_j { background: #002e6c;}' +
        'span.bulk_f { background: #832c40;}' +
        'span.bulk_p { background: #487c93;}' +
        'span.bulk_y { background: #016564;}' +
        '.flight_item span.stopover { color: #909090 !important; display: inline-block; background: white; font-size: 11px; margin: 0px 6px !important; }' +
        '.bulk_submit {background-color: #367778; border: none; color: white; vertical-align: middle; margin: 0px auto; height: 45px; line-height: 35px; padding: 5px 0; width: 100%; display: block;}' +
        '.bulk_submit img, button.uef_search img {line-height: 35px; height: 25px; display: inline-block; margin-right: 10px; vertical-align: -7px;}' +
        '.bulk_searching, .uef_search.searching  {background-color: #b9cdc9 !important;}' +
        '.col-select-departure-flight > .row:last-of-type { padding-bottom: 140px; }'
    );

    var lang = (navigator.language != "zh-TW") ? {
            "ec" : "HK",
            "el": "en",
            "search" : "Search",
            "coffee" : "Did this tool help you? 🥰 Buy me a coffee! ☕",
            "searching" : "<img src='https://book.cathaypacific.com/CathayPacificAwardV3/AML_S65.12/common/skin/img/icons/cx/icon-loading.gif'> Searching...",
            "next_10" : "Next 10 Days",
            "search_10" : "Batch Availability for 20 Days",
            "flights" : "Available Flights",
            "date" : "Date",
            "no_flights" : "No Flights",
            "expired" : "Search Next 20 (Requires Refresh)",
            "searching_cont" : "<img src='https://book.cathaypacific.com/CathayPacificAwardV3/AML_S65.12/common/skin/img/icons/cx/icon-loading.gif'> Please wait... (Page will refresh)",
            "super" : "SuperCharged Award Search"
        } : {
            "ec" : "TW",
            "el": "zh",
            "search" : "搜尋",
            "coffee" : "這工具有幫到你嗎?🥰 歡迎請我喝杯咖啡呀!☕",
            "searching" : "<img src='https://book.cathaypacific.com/CathayPacificAwardV3/AML_S65.12/common/skin/img/icons/cx/icon-loading.gif'> 請稍後...",
            "next_10" : "再搜 20 天",
            "search_10" : "批次搜尋 20 天可兌換航班",
            "flights" : "可兌換航班",
            "date" : "日期",
            "no_flights" : "無航班",
            "expired" : "再搜尋 20 天 (畫面需重整)",
            "searching_cont" : "<img src='https://book.cathaypacific.com/CathayPacificAwardV3/AML_S65.12/common/skin/img/icons/cx/icon-loading.gif'> 請稍後... (視窗將會刷新)",
            "super" : "SUPERCharged Award Search"
        };

    var cx_json = {
        "awardType": "Standard",
        "brand": "CX",
        "cabinClass": "Y",
        "entryCountry": lang.ec,
        "entryLanguage": lang.el,
        "entryPoint": "https://www.cathaypacific.com/cx/" + lang.el + "_" + lang.ec + "/book-a-trip/redeem-flights/redeem-flight-awards.html",
        "errorUrl": "https://www.cathaypacific.com/cx/" + lang.el + "_" + lang.ec + "/book-a-trip/redeem-flights/redeem-flight-awards.html?recent_search=ow",
        "isFlexibleDate": false,
        "numAdult": 1,
        "numChild": 0,
        "promotionCode": "",
        "returnUrl": "https://www.cathaypacific.com/cx/" + lang.el + "_" + lang.ec + "/book-a-trip/redeem-flights/redeem-flight-awards.html?recent_search=ow",
        "segments": [
            {
                "departureDate": "20230801",
                "origin": "TPE",
                "destination": "TYO"
            }
        ]
    };

    var bulk_continue = false;
    var limit_reached = false;

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });

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


    var cxhttp = new XMLHttpRequest();
    cxhttp.onload = function() {
        var response = JSON.parse(this.responseText);
        cxform(response.parameters);
    }

    function cxsearch(string){
        cxhttp.withCredentials = true;
        cxhttp.open("POST", "https://api.cathaypacific.com/redibe/standardAward/create");
        cxhttp.setRequestHeader("Content-type", "application/json");
        cxhttp.send(string);
    }

    function cxform(parameters) {

        // Create a form dynamically
        var form = document.createElement("form");
        var action = "https://book.cathaypacific.com/CathayPacificAwardV3/dyn/air/booking/availability";
        if (bulk_continue) action += "?bulk_continue=1"
        form.setAttribute("name", "cxform");
        form.setAttribute("method", "post");
        form.setAttribute("action", action);

        for(var item in parameters) {
            var input = document.createElement("input");
            input.setAttribute("type", "hidden");
            input.setAttribute("name", item);
            input.setAttribute("value", parameters[item]);
            form.appendChild(input);
        }

        document.getElementsByTagName("body")[0].appendChild(form);
        document.forms.cxform.submit();
    }

    function addtoBulk(html){
        document.querySelector(".bulk_table tbody").insertAdjacentHTML("beforeend", html);
    }

var bulk_http = new XMLHttpRequest();
var bulk_url = (typeof formSubmitUrl !== 'undefined') ? formSubmitUrl : "";
var bulk_date = "";
var bulk_limit = 21;
    bulk_http.onreadystatechange = function() {//Call a function when the state changes.
        if(bulk_http.readyState == 4 && bulk_http.status == 200) {
            bulk_date = bulk_date ? bulk_date : document.querySelector("input[name=uef_date]").value;
            var search_year = +bulk_date.substring(0, 4);
            var search_month = +bulk_date.substring(4, 6);
            var search_day = +bulk_date.substring(6, 8);
            var search_date = new Date(search_year, search_month - 1, search_day);
            var responseText = JSON.parse(bulk_http.responseText);
            var response = JSON.parse(responseText.pageBom);
            var flights = response.modelObject.availabilities.upsell.bounds[0].flights;
            var flightHTML = "";
            var formatted_date = search_date.getFullYear() +"-"+ (search_date.getMonth() +1).toString().padStart(2, '0') +"-"+ search_date.getDate().toString().padStart(2, '0');
            flightHTML += "<tr><td class='bulk_date'>";
            flightHTML += "<a href='javascript:void();' onclick='single_date(" + formatted_date.replaceAll("-","") + ")'>" + formatted_date + "</a>";
            flightHTML += "</td><td class='bulk_flights'>";
            var noflights = true;
            flights.forEach((flight) => {
                var available = "";
                if(flight.segments.length == 1) {
                    if (flight.segments[0].cabins?.F?.status >= 1) available = available + " <span class='bulk_f'>F <b>" + flight.segments[0].cabins.F.status + "</b></span>";
                    if (flight.segments[0].cabins?.B?.status >= 1) available = available + " <span class='bulk_j'>J <b>" + flight.segments[0].cabins.B.status + "</b></span>";
                    if (flight.segments[0].cabins?.E?.status >= 1) available = available + " <span class='bulk_p'>PY <b>" + flight.segments[0].cabins.E.status + "</b></span>";
                    if (flight.segments[0].cabins?.R?.status >= 1) available = available + " <span class='bulk_y'>Y <b>" + flight.segments[0].cabins.R.status + "</b></span>";
                    if (available != "") {
                        flightHTML += "<span class='flight_item direct'><img src='https://book.cathaypacific.com/CathayPacificAwardV3/AML_S65.12/common/skin/img/airlines/logo-" + flight.segments[0].flightIdentifier.marketingAirline.toLowerCase() + ".png'>" + flight.segments[0].flightIdentifier.marketingAirline + flight.segments[0].flightIdentifier.flightNumber + available + "</span>";
                        noflights = false;
                    }
                } else {
                    if (flight.segments[0].cabins?.F?.status >= 1 && flight.segments[1].cabins?.F?.status >= 1) available = available + " <span class='bulk_f'>F <b>" + Math.max(flight.segments[0].cabins.F.status, flight.segments[1].cabins.F.status) + "</b></span>";
                    if (flight.segments[0].cabins?.B?.status >= 1 && flight.segments[1].cabins?.B?.status >= 1) available = available + " <span class='bulk_j'>J <b>" + Math.max(flight.segments[0].cabins.B.status, flight.segments[1].cabins.B.status) + "</b></span>";
                    if (flight.segments[0].cabins?.E?.status >= 1 && flight.segments[1].cabins?.E?.status >= 1) available = available + " <span class='bulk_p'>PY <b>" + Math.max(flight.segments[0].cabins.E.status, flight.segments[1].cabins.E.status) + "</b></span>";
                    if (flight.segments[0].cabins?.R?.status >= 1 && flight.segments[1].cabins?.R?.status >= 1) available = available + " <span class='bulk_y'>Y <b>" + Math.max(flight.segments[0].cabins.R.status, flight.segments[1].cabins.R.status) + "</b></span>";
                    if (available != "") {
                        flightHTML += "<span class='flight_item'><img src='https://book.cathaypacific.com/CathayPacificAwardV3/AML_S65.12/common/skin/img/airlines/logo-" + flight.segments[0].flightIdentifier.marketingAirline.toLowerCase() + ".png'>" + flight.segments[0].flightIdentifier.marketingAirline + flight.segments[0].flightIdentifier.flightNumber + "<span class='stopover'>" + /^[A-Z]{3}:([A-Z:]{3,7}):[A-Z]{3}_/g.exec(flight.flightIdString)[1].replace(":"," / ") + "</span>" + flight.segments[1].flightIdentifier.marketingAirline + flight.segments[1].flightIdentifier.flightNumber + available + "</span>";
                        noflights = false;
                    }
                }
            });
            if (noflights) flightHTML += "<span class='bulk_no_flights'>"+lang.no_flights+"</span>";
            flightHTML += "</td></tr>";
            addtoBulk(flightHTML);
            var next_date = new Date(search_year, search_month - 1, search_day + 1);
            bulk_date = next_date.getFullYear() +""+ (next_date.getMonth() +1).toString().padStart(2, '0') +""+ next_date.getDate().toString().padStart(2, '0');
            bulk_search();
        } else if (bulk_http.readyState == 4 && bulk_http.status >= 400) {
            document.querySelector(".bulk_submit").innerHTML = lang.expired;
            limit_reached = true;
            bulk_limit = 21;
            document.querySelector(".bulk_submit").classList.remove("bulk_searching");
        }
    }

function single_date(date){
        document.querySelector("#uef_date").value = date;
        document.querySelector(".bulk_submit").innerHTML = lang.searching;
        document.querySelector(".uef_search").click();
}


function bulk_search() {
    if(limit_reached) {
        bulk_continue = true;
        document.querySelector("#uef_date").value = bulk_date;
        document.querySelector(".bulk_submit").innerHTML = lang.searching;
        document.querySelector(".uef_search").click();
        return;
    } else if (--bulk_limit <= 0) {
        document.querySelector(".bulk_submit").innerText = lang.next_10;
        document.querySelector(".bulk_submit").classList.remove("bulk_searching");
        return;
    }
    bulk_date = bulk_date ? bulk_date : document.querySelector("input[name=uef_date]").value;
    var search_year = +bulk_date.substring(0, 4);
    var search_month = +bulk_date.substring(4, 6);
    var search_day = +bulk_date.substring(6, 8);
    var search_date = new Date(search_year, search_month - 1, search_day);
    var new_date = search_date.getFullYear() +""+ (search_date.getMonth() + 1).toString().padStart(2, '0') +""+ search_date.getDate().toString().padStart(2, '0') + "0000";
    var params = document.querySelector("form[name=MNMB_FACADE_FORM] input[name=ENTRY_REQUEST]").value;
    params = params.replace('&ENCT=2', '').replace('&SERVICE_ID=1', '').replace('&DIRECT_LOGIN=YES', '').replace('&B_DATE_1', '&OLD_DATE').replace(/(&ENC=[^&]+)/g,"");
    params = params + "WDS_PRE_STEP=SCHEDULE_DRIVEN&WDS_CURRENT_STEP=SCHEDULE_DRIVEN&WDS_TIER_SELECTED=STD&WDS_SELECTED_TIER=STD&PAGE_TICKET=0&&WDS_DATE=" + new_date + "&DDS_PROMO_SELECTED=FALSE&B_DATE_1=" + new_date ;
    bulk_http.open('POST', bulk_url, true);

    //Send the proper header information along with the request
    bulk_http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    bulk_http.setRequestHeader('Accept', 'application/json, text/plain, */*');

    bulk_http.send(params);
}

    function insertBulk(elm){
        elm.insertAdjacentHTML("beforeend", "<div class='bulk_box bulk_box_hidden'><div>" +
                               "<table class='bulk_table bulk_hidden'><thead><th class='bulk_date'>" + lang.date + "</th><th class='bulk_flights'>" + lang.flights + "</th></thead><tbody></tbody></table>" +
                               "</div>" +
                               "<button class='bulk_submit'>" + lang.search_10 + "</button>" +
                               "</div>");

        document.querySelector(".bulk_submit").addEventListener("click",function(e){
            document.querySelector(".bulk_table").classList.remove("bulk_hidden");
            if (bulk_limit != 21 && bulk_limit != 0){
                bulk_limit = 0;
                document.querySelector(".bulk_submit").innerText = lang.next_10;
                document.querySelector(".bulk_submit").classList.remove("bulk_searching");
            } else {
                bulk_limit = 21;
                document.querySelector(".bulk_submit").innerHTML = lang.searching;
                document.querySelector(".bulk_submit").classList.add("bulk_searching");
                bulk_search();
            }
        });

        if(GM_getValue("bulk_show") == true || localStorage.getItem("bulk_show") == true) {
                document.querySelector(".unelevated_title").innerText = lang.super;
                document.querySelector(".bulk_box").classList.remove("bulk_box_hidden");
        }

        const queryString = window.location.search;
        const urlParams = new URLSearchParams(queryString);
        const bcont_query = urlParams.get('bulk_continue');
        if (bcont_query) {
            document.querySelector(".bulk_submit").innerHTML = lang.searching;
            document.querySelector(".bulk_submit").classList.add("bulk_searching");
            waitForElm('form[name=MNMB_FACADE_FORM] input[name=ENTRY_REQUEST]').then((elm) => {
                setTimeout(() => {
                    document.querySelector(".bulk_submit").click();
                }, "1000")
            });
        }
    }

    function insertForm(elm){
        elm.insertAdjacentHTML("beforeend", "<div class='unelevated_form'>" +
                               "<div class='unelevated_title'>Unelevated Award Search</div>" +
                               "<label><span>From</span><input type='text' onClick='this.select();' id='uef_from' name='uef_from' placeholder='TPE' value='" + uef_from + "'></label>" +
                               "<label><span>To</span><input type='text' onClick='this.select();' id='uef_to' name='uef_to' placeholder='TYO' value='" + uef_to + "'></label>" +
                               "<label><span>Adults</span><input type='text' onClick='this.select();' id='uef_adult' name='uef_adult' placeholder='Adults' value='" + uef_adult + "'></label>" +
                               "<label><span>Children</span><input type='text' onClick='this.select();' id='uef_child' name='uef_child' placeholder='Children' value='" + uef_child + "'></label>" +
                               "<label><span>Date</span><input class='uef_date' onClick='this.select();' id='uef_date' type='text' name='uef_date' placeholder='20221001' value='" + uef_date + "'></label>" +
                               "<button class='uef_search'>" + lang.search + "</button>" +
                               "<div class='unelevated_sub'><a href='https://jayliu.net/buymeacoffee' target='_blank'>" + lang.coffee + "</a></div>" +
                               //"<a href='javascript:void();' class='uef_toggle'></a>" +
                               "</div>");

        /*document.querySelector(".uef_toggle").addEventListener("click",function(e){
        document.querySelector(".unelevated_form").classList.toggle("uef_collapsed")
    });*/

        document.querySelector(".unelevated_form input[name=uef_from]").addEventListener("change",function(e){
            this.value = this.value.toUpperCase();
        });
        document.querySelector(".unelevated_form input[name=uef_to]").addEventListener("change",function(e){
            this.value = this.value.toUpperCase();
        });
        var secret_mode = 0;
        document.querySelector(".unelevated_title").addEventListener("click",function(e){
            if(++secret_mode == 9) {
                document.querySelector(".unelevated_title").innerText = lang.super;
                document.querySelector(".bulk_box").classList.remove("bulk_box_hidden");
                localStorage.setItem("bulk_show",true);
                GM_setValue("bulk_show",true);
            }
        });


        document.querySelector(".uef_search").addEventListener("click",function(e){
            uef_from = document.querySelector("#uef_from").value;
            uef_to = document.querySelector("#uef_to").value;
            uef_date = document.querySelector("#uef_date").value;
            uef_adult = document.querySelector("#uef_adult").value;
            uef_child = document.querySelector("#uef_child").value;
            localStorage.setItem("uef_from",uef_from);
            localStorage.setItem("uef_to",uef_to);
            localStorage.setItem("uef_date",uef_date);
            localStorage.setItem("uef_adult",uef_adult);
            localStorage.setItem("uef_child",uef_child);
            GM_setValue("uef_from",uef_from);
            GM_setValue("uef_to",uef_to);
            GM_setValue("uef_date",uef_date);
            GM_setValue("uef_adult",uef_adult);
            GM_setValue("uef_child",uef_child);
            cx_json.numAdult = uef_adult;
            cx_json.numChild = uef_child;
            cx_json.segments[0].departureDate = uef_date;
            cx_json.segments[0].origin = uef_from.toUpperCase();
            cx_json.segments[0].destination = uef_to.toUpperCase();
            var cx_string = JSON.stringify(cx_json);
            document.querySelector(".uef_search").innerHTML = lang.searching;
            document.querySelector(".uef_search").classList.add("searching");
            cxsearch(cx_string);
        });
    }

waitForElm('.redibe-v3-flightsearch-description').then((elm) => {
    insertForm(elm);
});

waitForElm('.bound-route').then((elm) => {
    insertForm(elm);
    insertBulk(elm);
});
})();