HSBC travel portal 查看OTA脚本
脚本可显示HSBC旅行门户酒店房型的OTA来源。
1. 关键信息
- 作者 @windrunner 发布 Tampermonkey 脚本,可拦截 HSBC travel portal API,在页面注入 Inventory Type(AGD=Agoda, PCLN=Priceline, BKG=Booking)(#1)
- 脚本通过匹配标题、价格、支付类型、描述来定位房型,若多个 OTA 有相同房型会显示多个类型 (#1)
- 需开启浏览器插件开发者模式 (#14),部分酒店不显示 Inventory Type 可能因数据或脚本限制 (#25 #26)
- “pay when you stay” 选项以 Booking 为主,Agoda 也有,Priceline 较少 (#15 #17 #21 #22)
2. 羊毛/优惠信息
- HSBC Elite 卡 $400 travel credit(#3 #11)
- HSBC travel portal 预订可累积 3X 积分,过快取消可能导致积分被回收(#1)
- 使用 pay when you stay 时无需在卡上预留 8k+ 额度(#11 #12)
3. 最新动态
- 脚本已搬运至 lounge 并持续可用(#4)
- 发现所有 POSTPAID 均为 BKG(Booking),Agoda 的“book now, pay later”在 portal 中搜不到(#22)
- Agoda 的 Inventory Type 近期不再显示(#27),脚本可能因页面更新需刷新或重新登录(#26)
4. 争议或不同意见
- 部分用户认为 HSBC 的 AML/Fraud review 团队是最大阻碍(#5)
- 脚本匹配限制可能导致同一房型显示多个 OTA,需用户自行判断(#1)
- 过快取消是否一定触发问题存在争议,作者建议谨慎操作(#1 #10 #22)
5. 行动建议
- 安装 Tampermonkey 并启用开发者模式后添加脚本,刷新 portal 页面使用(#14)
- 预订前务必查看 Cancellation Policy,优先选择 pay when you stay 且不要过快取消(#1)
- 若房型显示多个 Inventory Type,建议轮流使用 Booking 和 Agoda,避免 Priceline(可能需电话退订)(#10)
- 使用 $400 credit 时无需保留大额可用额度(#12)
因为经常有人问,加上最近又有好几个人来问我,于是弄了个脚本。 以下脚本配合tampermonkey食用 脚本代码 // ==UserScript== // @name HSBC Travel Inventory Type // @namespace http://tampermonkey.net/ // @version 1.0 // @description HSBC Travel Inventory Type // @author lol // @match https://hsbc-travel-membersite.podiumrewards.com/* // @grant none // @run-at document-start // ==/UserScript== (function () { 'use strict'; if (!location.hash.startsWith("#/hotels/detail/")) { return; } console.log("HSBC Inventory Script Loaded."); // Intercept XMLHttpRequest (function () { const originalXHR = window.XMLHttpRequest; function ModifiedXHR() { const xhr = new originalXHR(); // Intercept 'onreadystatechange' const originalOpen = xhr.open; xhr.open = function (method, url, ...args) { // Check if the request is for the target API this.isTargetAPI = url.includes('/api/v1/hotel/view'); return originalOpen.apply(this, [method, url, ...args]); }; const originalSend = xhr.send; xhr.send = function (...args) { if (this.isTargetAPI) { this.addEventListener('readystatechange', function () { if (this.readyState === 4 && this.status === 200) { try { const data = JSON.parse(this.responseText); waitForRoomContainers(() => processApiResponse(data)); } catch (err) { console.error('Error parsing API response:', err); } } }); } return originalSend.apply(this, args); }; return xhr; } window.XMLHttpRequest = ModifiedXHR; })(); // Intercept fetch const originalFetch = window.fetch; window.fetch = async function (...args) { const response = await originalFetch(...args); // Check if the request matches the target API if (args[0].includes('/api/v1/hotel/view')) { // Clone the response to parse its data const clonedResponse = response.clone(); clonedResponse.json().then(data => { waitForRoomContainers(() => processApiResponse(data)); }); } return response; }; // Function to compare floating-point numbers with an epsilon function floatEquals(a, b, epsilon = 1e-2) { return Math.abs(a - b) < epsilon; } // Wait for room-container elements to exist function waitForRoomContainers(callback, maxRetries = 20, interval = 500) { let retries = 0; const checkExist = setInterval(() => { const containers = document.querySelectorAll('.room-container'); if (containers.length > 0) { clearInterval(checkExist); callback(); } else if (retries >= maxRetries) { clearInterval(checkExist); console.error('Timeout: room-container elements not found.'); } retries++; }, interval); } // Process API response and inject inventory_type function processApiResponse(data) { try { const roomData = data.data.hotel_data[0].room_data; // Iterate through the room data from API roomData.forEach(apiRoom => { const apiTitle = apiRoom.rate_data[0]?.title; // Get the title const apiPrice = apiRoom.rate_data[0]?.price_details.display_price; // Get the price const inventoryType = apiRoom.rate_data[0]?.inventory_type; // Get the inventory type const apiPaymentType = apiRoom.rate_data[0]?.payment_type; // Get payment type (POSTPAID, PREPAID) const apiDescription = apiRoom.rate_data[0]?.description.trim(); // Get description // Determine payment type string from API const apiPaymentText = apiPaymentType === "PREPAID" ? "Prepaid - Book Now, Pay Now" : "Book now, pay when you stay"; // console.log(`title ${apiTitle}, price ${apiPrice}, inventoryType ${inventoryType}`); // Match the API data with DOM elements document.querySelectorAll('.room-container').forEach(container => { const roomTitle = container.querySelector('.h5.regular')?.textContent.trim(); const roomPriceText = container.querySelector('.price-position .payment-span')?.textContent.trim(); const roomPrice = roomPriceText ? parseFloat(roomPriceText.replace("$", "").replace(",", "").split(" ")[0]) : NaN; // Dynamically locate payment type element const paymentTypeElement = Array.from(container.querySelectorAll('li')) .find(li => li.id.startsWith('room-payment-type')); const roomPaymentText = paymentTypeElement ? paymentTypeElement.textContent.trim() : ''; // Dynamically locate description element const descriptionElement = container.querySelector('.col-sm-12 p'); const roomDescription = descriptionElement ? descriptionElement.textContent.trim() : ''; // console.log(`title ${roomTitle}, price ${roomPrice}`); // Compare the API title and price with the page title and price if (roomTitle === apiTitle && floatEquals(roomPrice, parseFloat(apiPrice)) && roomPaymentText.includes(apiPaymentText) && roomDescription === apiDescription) { console.log(`Matched ${roomTitle}`); // Append the inventory type to the room details const detailsList = container.querySelector('.col-sm-5.details ul'); if (detailsList) { const inventoryItem = document.createElement('li'); inventoryItem.className = 'inventory-type'; inventoryItem.innerHTML = `<strong>Inventory Type:</strong> ${inventoryType}`; detailsList.appendChild(inventoryItem); } } }); }); } catch (err) { console.error("Error processing API response:", err); } } })(); 效果如下 /uploads/short-url/lRWkWfTxTDLnzCpsG8uz7AdpTZX.png?dl=1 AGD是Agoda,PCLN是Priceline,BKG是Booking 因为脚本匹配方式的限制,一个房型里可能显示多个Inventory Type,如果显示为同一个OTA那么问题不大,如果显示不同的平台 说明不同OTA里都有一样的房型(标题,描述,价格,prepaid/postpaid均一样),脚本不能确定具体是哪个OTA 预定前请看好Cancellation Policy,如果不打算去住推荐订pay when you stay的价格,并且不要太快取消。实测Booking有概率同步取消信息给HSBC(如果取消的太快),这种情况多扣除的3X会被返还,但很可能会增大风险(正常情况的计算 https://www.uscardforum.com/t/topic/147780 )
感谢大佬,希望Elite能活久一点
感谢大佬,正好打算把今年的400credit拿了,好人一生平安
把之前帖子也搬进lounge来了,希望能一直活下去
感觉最大的阻碍是HSBC搞AML/Fraud review的三哥三姐们
感谢大佬。 刚测试,非常方便, 一目了然 /uploads/short-url/yJSGKCZCnXNMbbgCg9xZgxRISGd.jpeg?dl=1
而你 我的朋友 才是真正的英雄
hsbc这个api是真的难蚌,我搜芝加哥四季能返回100多个房型,每个房型下面显示四个BKG,疑似重复返回四遍booking的结果 貌似全是重复的
所以最好是薅Priceline或者Agoda的?
priceline可能要打电话退 booking和agoda轮流定 别太快取消基本就没问题
还没实操8k pay when stay 想问一下 如果是pay when stay的话 400 credit post报销 不用留8k+额度在卡上是吧?
不需要留额度
这个怎么用啊?在Tampermonkey里create a new script, 然后copy/paste 到script window, 然后save file. installed userscripts tab里多出一个script,已经enable了。refresh 网页没有变化。
打开网页之后tampermonkey有显示script正在运行么? 也有可能是浏览器插件的developer mode没开,我之前用edge,tampermonkey提示我要打开才能用
刷了好几个,发现有pay when you stay的都是booking,这个是巧合还是只有booking有这个功能
居然有comments, 楼主好人
agoda也有 priceline好像没咋看到
因为太懒 所以让gpt写了大部分 gpt写了很多注释
看到了。原来不是所有的房型都有 Inventory Type ,没有这个的是不知道type吗?
一点建议的话,直接把平台 显示在pay when you stay 那里,直接ctrl + f, pay when you stay 带上平台能精准定位
same,刷了10个城市每个城市10个酒店,也是发现有pay when you stay的都是booking
same. 目前搜索所有POSTPAID的都是BKG的。 直接在agoda上搜book now, pay later的酒店房型在hsbc travel里都搜不到,可能是被屏蔽了。 结合之前dp: windrunner: 实测Booking有概率同步取消信息给HSBC 莫非这就是故意的,防止先定再取消的办法?
windrunner: 预定前请看好Cancellation Policy,如果不打算去住推荐订pay when you stay的价格,并且不要太快取消。实测Booking有概率同步取消信息给HSBC(如果取消的太快),这种情况多扣除的3X会被返还,但很可能会增大风险(正常情况的计算 https://www.uscardforum.com/t/topic/147780 ) 感谢大佬的脚本 现在已经能够看到三种不同的PROVIDER, 但是还是找不到PAY WHEN STAY啊?一口气看了几十个都没有 蚌埠住了 比如找到一个有退款选项的 /uploads/short-url/kh8G8IFYrorgq4P7jd0bdBx5hoW.jpeg?dl=1 一个个点进去之后 /uploads/short-url/4CZP2hYCxcQMcEICNkZRq0bofIK.png?dl=1
之前沒看到這個神帖 跟Priceline交流了好一陣子 謝過大哥,好人一生平安
不過想請問沒有每一個選項都有Inventory Type是正常的嗎?
update: 退出hsbc重新打开后可以了 请问op是挂了吗 /uploads/short-url/jbz2cRzXkgctpXGm4ricSMWJOru.jpeg?dl=1 脚本正常启动,所有的酒店都不显示Inventory Type
我发现现在只有 priceline 和 booking 显示了诶,agoda 是不是都不显示了?