[AS]里程票批量搜索和结果增强显示 — AS Enhancer + Batch Search 【4/15 更新V1.2版本:UI大改,增加大量过滤】
AS搜索增强与批量搜索脚本使用指南及最新修复
1. 关键信息
- 两个脚本互不干涉:AS Enhancer(结果增强)+ Batch Search(批量搜索)。
- AS 不需登录;登录后不建议使用脚本。
- 需开启
allow user scripts并打开 Dev Mode 安装。 - 增强功能:Booking Code 显示、机型/中转/里程显示、排序、过滤、日历热力图。
- 批量搜索:多出发地×目的地、日期范围、舱位/税费/人数/直飞过滤、按里程排序。
- 已知 Bug:单航班点不开;已修复(#106)。
2. 羊毛/优惠信息
无
3. 最新动态
- v1.2 UI 大改,增加大量过滤与日历视图(#1)。
- 已修复单航班无法展开问题(#104-#106)。
- 更新后需重新安装并开启用户脚本(#98)。
4. 争议或不同意见
- 有人反映批量搜索突然停止(#99);建议手动搜一张票再试。
- 日历视图在现金与里程票切换后曾显示错误(#85);已修复。
5. 行动建议
- 安装前确认开启
allow user scripts与 Dev Mode。 - 先用 Incognito 测试,确认脚本生效再登录。
- 遇到点不开航班先刷新或重新安装脚本。
相信大家都知道AS官网搜里程票有多么操蛋,并且得益于其完全不设防的官网 ,遂以CC辅助我搓了两个油猴脚本:
这两个脚本互不干涉,可以同时安装也可以只使用一个
以下部分说明内容由AI生成:
工具一:AS Enhancer(搜索结果增强)
装上之后,AS的里程票搜索结果页会自动增强:
功能:
Booking Code显示 — 每个票价格子直接显示舱位代码,比如 X(Y) I(J),不用再点进去看
航班信息增强 — 机型、中转时间、总里程一目了然
排序 — 类似AA官网的排序逻辑,点击任意票价列头,按里程数升序/降序排列
过滤 — 勾选"Available only"只显示对应cabin有票的航班
日历视图 — 替换AS原生的日期选择器,改为显示两个月的里程价格热力图
预览:
Screenshot 2026-03-21 0004502880×1462 326 KB
工具二:AS Batch Award Search(批量里程票搜索)
这个会在AS搜索界面增加一个浮动面板,支持批量搜索多条航线的里程票。
核心功能:
多出发地 × 多目的地 — 输入 KIX, NRT, ICN → SAN, LAX, SFO,自动搜索所有9条航线组合
日期范围 — 选择起止月份,最远支持330天
分舱位里程上限 — 默认 Main 50K、Business 95K、First 110K
税费上限 — 设置最大现金部分,过滤掉税费太高的票
人数 — 支持1-9人,过滤座位不够的航班
Nonstop筛选 — 只看直飞
搜索流程:
日历扫描(Phase 1)— 并行请求所有航线×月份的日历数据,找出里程数低于上限的日期
自动拉取(Phase 2)— 对符合条件的日期,自动拉取详细航班信息
过滤 — 日历显示有票但实际没有对应价格的,自动标记并过滤。(并不能处理真的幽灵票,只能筛掉日历视图中的假票)
显示效果(类似国泰神器风格):
按航线分组,可折叠
每个航班显示样式:航司Logo + 航班号 + 中转机场 + 飞行时间 + 舱位徽章(J 95K / F 110K)
点击展开看详细信息:每段航班时间、机型、实际承运、剩余座位、Booking Code
不同机场换乘(如NRT→HND)红色警告
混合舱位(如一段J一段Y)标记 * 并提示
其他:
保存最近5次搜索记录,一键重搜
搜索参数本地化存储
Codeshare自动合并
预览:
Screenshot 2026-03-21 0006212880×1460 223 KB
Screenshot 2026-03-21 0008072880×1462 275 KB
Disclaimer:
不建议在登录账户的情况下使用此脚本,不登录不影响任何功能,本人不对使用此脚本造成任何后果及损失负责
本来准备放Github,想了下还是先放这,太长拆开放在楼下:
先赞后看
第二个太长放不下,直接放pastebin了:
pastebin.com
https://pastebin.com/3JuJrjQN
密码 uscardno1
前排前排
厉害了!
AS Enhancer
// ==UserScript==
// @name AS Enhancer
// @namespace http://tampermonkey.net/
// @version 8.1
// @description Sort, filter, and enrich Alaska Airlines award search results + calendar
// @author zpahai
// @match https://www.alaskaair.com/search/results*
// @grant none
// @run-at document-idle
// @updateURL https://raw.githubusercontent.com/AhaiMk01/as_enhanced/main/as.js
// @downloadURL https://raw.githubusercontent.com/AhaiMk01/as_enhanced/main/as.js
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/* ─── Constants ─── */
const CABIN_LETTER = { COACH:'Y', BUSINESS:'J', FIRST:'F', PREMIUM:'W', PREMIUM_ECONOMY:'W' };
const SOLUTION_PREFIX = 'REFUNDABLE_';
const POLL_INTERVAL = 800;
const POLL_TIMEOUT = 20000;
const CAL_MAX_DAYS = 330;
/* ─── State ─── */
let sortCol = null, sortDir = 0;
let activeFilters = new Set();
let flightData = null;
let initRunning = false;
let calDateCache = {};
let calDisplayedMonths = [];
let calSelectedDate = null;
/* ─── CSS ─── */
function injectStyles() {
if (document.getElementById('as-enhancer-css')) return;
const style = document.createElement('style');
style.id = 'as-enhancer-css';
style.textContent = `
.fare-header-container button.header { pointer-events: auto !important; }
.as-sort-arrow { display: inline; margin-left: 3px; font-size: 13px; }
.as-filter-row {
display: flex; align-items: center; justify-content: center;
gap: 4px; margin-top: 2px; font-size: 11px; font-weight: 500;
line-height: 1; pointer-events: auto !important; position: relative; z-index: 10;
}
.as-filter-cb {
width: 14px; height: 14px; cursor: pointer; accent-color: #0074c8;
margin: 0; pointer-events: auto !important; position: relative; z-index: 11;
}
.as-filter-label {
cursor: pointer; user-select: none; opacity: 0.75; font-size: 11px;
pointer-events: auto !important;
}
.as-clear-filters {
display: inline-flex; align-items: center; gap: 3px;
padding: 2px 10px; border: 1px solid rgba(255,255,255,0.6); border-radius: 12px;
background: rgba(255,255,255,0.15); color: white; font-size: 11px; font-weight: 600;
cursor: pointer; pointer-events: auto !important; transition: background 0.15s;
position: relative; z-index: 10;
}
.as-clear-filters:hover { background: rgba(255,255,255,0.3); }
.as-clear-filters.hidden { display: none; }
.as-enrich {
display: flex; flex-direction: column; gap: 1px;
margin-top: 2px; padding-top: 2px;
font-size: 10px; line-height: 1.3; color: #444;
}
.as-enrich-row { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.as-tag {
display: inline-block; padding: 1px 5px; border-radius: 3px;
font-size: 10px; font-weight: 600; font-family: monospace;
}
.as-tag-booking { background: #e0f0ff; color: #0055a5; }
.as-tag-aircraft { background: #e8f5e9; color: #2e7d32; }
.as-seg-info { font-size: 9px; color: #666; padding: 2px 12px 4px; }
.as-seg-line-compact {
display: flex; align-items: center; gap: 3px; flex-wrap: wrap; line-height: 1.4;
}
.as-seg-line-compact .as-tag-aircraft { font-size: 9px; padding: 0 4px; }
.as-dot { color: #999; }
.as-layover { color: #c55; font-weight: 600; }
.as-distance { color: #888; }
.as-low-fare { color: #2e7d32; font-weight: 600; font-size: 9px; }
.flight-card-badges { display: none !important; }
.flight-card-details { padding: 8px 12px !important; }
.flight-header { padding: 2px 0 !important; }
.flight-info { gap: 0 !important; }
.disclosure-container { padding: 0 !important; margin: 0 !important; }
.disclosure-text { font-size: 11px !important; line-height: 1.3 !important; margin: 0 !important; }
.full-width-divider { margin: 4px 0 !important; }
.action-buttons { padding: 2px 0 !important; margin: 0 !important; gap: 4px !important; }
.action-buttons button, .action-buttons [type="button"] {
padding: 2px 10px !important; font-size: 11px !important;
min-height: unset !important; height: auto !important;
}
.flight-card-content { gap: 0 !important; }
.flight-card-details-section { gap: 0 !important; }
.shoulder-dates-wrapper { display: none !important; }
#as-cal-wrap { margin: 0 auto 8px; max-width: 820px; }
#as-cal-nav {
display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;
}
#as-cal-nav button {
background: none; border: 1px solid #ccc; border-radius: 4px;
padding: 2px 10px; cursor: pointer; font-size: 16px;
}
#as-cal-nav button:hover { background: #f0f0f0; }
#as-cal-nav span { font-weight: 600; font-size: 14px; }
#as-cal { transition: opacity .2s; }
.as-cal-cell {
border-radius: 4px; padding: 2px 1px; text-align: center;
min-height: 32px; display: flex; flex-direction: column; justify-content: center;
font-size: 10px; line-height: 1.2; cursor: default;
}
.as-cal-cell.clickable { cursor: pointer; }
.as-cal-cell.clickable:hover { outline: 2px solid #003366; outline-offset: -1px; }
`;
document.head.appendChild(style);
}
/* ─── SvelteKit devalue decoder ─── */
function devalueRevive(data) {
const c = new Map();
function r(i) {
if (i < 0) return i === -1 ? undefined : i === -2 ? NaN : i === -3 ? Infinity
: i === -4 ? -Infinity : undefined;
if (c.has(i)) return c.get(i);
const v = data[i];
if (v === null || typeof v !== 'object') { c.set(i, v); return v; }
if (Array.isArray(v)) { const a = v.map(r); c.set(i, a); return a; }
const o = {}; c.set(i, o);
for (const [k, j] of Object.entries(v)) o[k] = r(j);
return o;
}
return r(0);
}
/* ─── Inline resolve script parser ─── */
function parseResolveData(resolveId) {
const scripts = document.querySelectorAll('script:not([src])');
const pattern = new RegExp('\\.resolve\\(\\s*' + resolveId + '\\s*,\\s*\\(\\)\\s*=>\\s*');
for (const s of scripts) {
const text = s.textContent;
const m = text.match(pattern);
if (!m) continue;
// Extract data using balanced paren tracking to find the closing )
const start = m.index + m[0].length;
let depth = 1, i = m.index + m[0].length - 1; // start after the outer (
// Actually, the outer ( is from resolve(2, () => DATA) — we need to find the matching )
// Reset: find the resolve( opening paren, track depth from there
const resolveStart = text.indexOf('(', m.index);
depth = 1;
for (i = resolveStart + 1; i < text.length; i++) {
if (text[i] === '(') depth++;
else if (text[i] === ')') { depth--; if (depth === 0) break; }
}
if (depth !== 0) continue;
const dataStr = text.substring(start, i);
try {
const data = (new Function('return ' + dataStr))();
return data;
} catch (e) { console.warn('AS Enhancer: resolve(' + resolveId + ') eval failed', e); }
}
return null;
}
/* ─── Data extraction ─── */
async function extractFlightData() {
// Method 1: parse the inline resolve(2) script from the DOM (instant)
try {
const data = parseResolveData(2);
if (data && Array.isArray(data) && data[0]?.rows) return data;
} catch (e) { console.warn('AS Enhancer: inline resolve parse failed', e); }
// Method 2: fallback network fetch (slow, triggers full server search)
try {
const params = new URLSearchParams(window.location.search);
const resp = await fetch('/search/results/__data.json?' + params.toString());
const text = await resp.text();
const lines = text.split('\n').filter(l => l.trim());
if (lines[2]) {
const chunk2 = JSON.parse(lines[2]);
const decoded = devalueRevive(chunk2.data);
if (decoded && decoded.rows) return [decoded];
}
} catch (e) { console.warn('AS Enhancer: __data.json failed', e); }
return null;
}
/* ─── Matching helpers ─── */
function parseDuration(text) {
const m = (text || '').match(/(\d+)h\s*(\d*)m?/);
return m ? parseInt(m[1]) * 60 + (parseInt(m[2]) || 0) : 0;
}
function getLocalTimeFromISO(isoStr) {
// Extract local time directly from ISO string, ignoring timezone conversion
const m = isoStr.match(/T(\d{2}):(\d{2})/);
return m ? parseInt(m[1]) * 60 + parseInt(m[2]) : -1;
}
function parseTimeToMinutes(timeStr) {
const m = timeStr.match(/(\d{1,2}):(\d{2})\s*(am|pm)/i);
if (!m) return -1;
let h = parseInt(m[1]);
const min = parseInt(m[2]);
const ap = m[3].toLowerCase();
if (ap === 'pm' && h !== 12) h += 12;
if (ap === 'am' && h === 12) h = 0;
return h * 60 + min;
}
/* ─── Match displayed rows to data ─── */
function matchRowToData(tr, dataRows) {
const card = tr.querySelector('[data-testid*="flight-card"]');
if (!card) return null;
const texts = card.textContent;
// Method 1: all flight numbers in text + duration
for (const dr of dataRows) {
const flights = dr.segments.map(s =>
s.publishingCarrier.carrierCode + ' ' + s.publishingCarrier.flightNumber
).join(', ');
if (texts.includes(flights) &&
Math.abs(dr.duration - parseDuration(texts.match(/(\d+h\s*\d*m)/)?.[1] || '')) < 5) {
return dr;
}
}
// Method 2: first segment flight number + duration
for (const dr of dataRows) {
const first = dr.segments[0].publishingCarrier;
const flightStr = first.carrierCode + ' ' + first.flightNumber;
if (texts.includes(flightStr)) {
const durMatch = texts.match(/(\d+h\s*\d*m)/);
if (durMatch && Math.abs(dr.duration - parseDuration(durMatch[1])) < 5) return dr;
}
}
// Method 3: departure time + duration (timezone-safe, for cards without flight numbers)
const depEl = card.querySelector('.departure-time');
const durMatch = texts.match(/(\d+h\s*\d*m)/);
if (depEl && durMatch) {
const cardDepMin = parseTimeToMinutes(depEl.textContent.trim());
const cardDur = parseDuration(durMatch[1]);
if (cardDepMin >= 0 && cardDur > 0) {
for (const dr of dataRows) {
const dataDepMin = getLocalTimeFromISO(dr.segments[0].departureTime);
if (Math.abs(cardDepMin - dataDepMin) < 2 &&
Math.abs(cardDur - dr.duration) < 5) {
return dr;
}
}
}
}
return null;
}
/* ─── Enrich fare tiles ─── */
function enrichTile(tile, sol, segments) {
if (tile.querySelector('.as-enrich')) return;
if (!sol) return;
if (tile.classList.contains('sold-out-button') || tile.textContent.includes('Unavailable')) return;
let html = '<div class="as-enrich"><div class="as-enrich-row">';
sol.bookingCodes.forEach((code, i) => {
const cabin = sol.cabins[i] || '';
const cabLetter = CABIN_LETTER[cabin] || cabin.charAt(0);
const seg = segments[i];
const route = seg ? seg.departureStation + '→' + seg.arrivalStation : '';
const cabinShort = cabin.charAt(0) + cabin.slice(1).toLowerCase();
html += `<span class="as-tag as-tag-booking" title="${code} (${cabinShort}) • ${route}">${code}(${cabLetter})</span>`;
});
html += '</div></div>';
tile.insertAdjacentHTML('beforeend', html);
}
/* ─── Enrich segment info ─── */
function enrichSegments(card, dataRow) {
if (card.querySelector('.as-seg-info')) return;
const segments = dataRow.segments;
let parts = [];
segments.forEach((seg, i) => {
const route = seg.departureStation + '→' + seg.arrivalStation;
const equip = seg.aircraftCode || seg.equipment?.code || seg.legs?.[0]?.equipment?.code || '';
parts.push(`<span style="color:#666">${route}</span>`);
if (equip) parts.push(`<span class="as-tag as-tag-aircraft">${equip}</span>`);
if (i < segments.length - 1) {
const arrTime = seg.arrivalTime || seg.legs?.[seg.legs.length - 1]?.arrivalTime;
const nextDep = segments[i + 1].departureTime || segments[i + 1].legs?.[0]?.departureTime;
if (arrTime && nextDep) {
const diff = (new Date(nextDep) - new Date(arrTime)) / 60000;
if (diff > 0) {
const h = Math.floor(diff / 60), m = Math.round(diff % 60);
parts.push(`<span class="as-dot">·</span>`);
parts.push(`<span class="as-layover">${h}h ${m}m</span>`);
}
}
parts.push(`<span class="as-dot">·</span>`);
}
});
if (dataRow.totalDistance) {
const dist = dataRow.totalDistance;
const len = typeof dist === 'object' ? dist.length : dist;
const unit = typeof dist === 'object' ? dist.unit : 'MI';
if (len) {
parts.push(`<span class="as-dot">·</span>`);
parts.push(`<span class="as-distance">${Number(len).toLocaleString()} ${unit}</span>`);
}
}
const badge = card.querySelector('.flight-card-badges .badge-text');
if (badge && badge.textContent.trim()) {
parts.push(`<span class="as-dot">·</span>`);
parts.push(`<span class="as-low-fare">🟢 ${badge.textContent.trim()}</span>`);
}
const div = document.createElement('div');
div.className = 'as-seg-info';
div.innerHTML = `<div class="as-seg-line-compact">${parts.join(' ')}</div>`;
const details = card.querySelector('.flight-card-details');
if (details) details.parentElement.insertBefore(div, details.nextSibling);
}
/* ─── Column header setup (sort + filter) ─── */
function setupHeaders() {
const containers = document.querySelectorAll('.fare-header-container');
if (!containers.length) return;
const lastContainer = containers[containers.length - 1];
containers.forEach(container => {
const btn = container.querySelector('button.header');
if (!btn || btn.dataset.asReady) return;
btn.dataset.asReady = '1';
const nameEl = btn.querySelector('.heading, p.heading');
const colId = container.id;
let arrow = btn.querySelector('.as-sort-arrow');
if (!arrow) {
arrow = document.createElement('span');
arrow.className = 'as-sort-arrow';
if (nameEl) nameEl.appendChild(arrow);
}
container.addEventListener('click', function (e) {
if (e.target.closest('.as-filter-row') || e.target.closest('.as-clear-filters')) return;
e.stopImmediatePropagation();
e.preventDefault();
if (sortCol === colId) {
sortDir = (sortDir % 3) + 1;
if (sortDir === 3) { sortCol = null; sortDir = 0; }
} else {
sortCol = colId; sortDir = 1;
}
updateSortArrows();
applySort();
}, true);
if (!btn.querySelector('.as-filter-row')) {
const filterRow = document.createElement('div');
filterRow.className = 'as-filter-row';
filterRow.addEventListener('click', e => e.stopImmediatePropagation(), true);
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'as-filter-cb';
cb.addEventListener('change', () => {
setTimeout(() => {
if (cb.checked) activeFilters.add(colId);
else activeFilters.delete(colId);
applyFilter();
}, 0);
});
const label = document.createElement('span');
label.className = 'as-filter-label';
label.textContent = 'Available only';
label.addEventListener('click', (e) => {
e.preventDefault();
cb.checked = !cb.checked;
cb.dispatchEvent(new Event('change'));
});
filterRow.appendChild(cb);
filterRow.appendChild(label);
btn.appendChild(filterRow);
}
if (container === lastContainer && !btn.querySelector('.as-clear-filters')) {
const clearBtn = document.createElement('button');
clearBtn.className = 'as-clear-filters hidden';
clearBtn.textContent = '✕ Clear filters';
clearBtn.addEventListener('click', e => {
e.stopImmediatePropagation();
e.preventDefault();
activeFilters.clear();
document.querySelectorAll('.as-filter-cb').forEach(c => c.checked = false);
applyFilter();
}, true);
btn.appendChild(clearBtn);
}
});
}
function updateSortArrows() {
document.querySelectorAll('.fare-header-container').forEach(c => {
const arrow = c.querySelector('.as-sort-arrow');
if (!arrow) return;
if (c.id === sortCol) {
arrow.textContent = sortDir === 1 ? ' ↑' : sortDir === 2 ? ' ↓' : '';
} else {
arrow.textContent = '';
}
});
}
/* ─── Sort logic ─── */
function getPoints(row, colId) {
const tiles = row.querySelectorAll('button[data-testid*="valuetile"]');
const headers = document.querySelectorAll('.fare-header-container');
let colIdx = -1;
headers.forEach((h, i) => { if (h.id === colId) colIdx = i; });
if (colIdx < 0 || colIdx >= tiles.length) return Infinity;
const tile = tiles[colIdx];
if (tile.classList.contains('sold-out-button') || tile.textContent.includes('Unavailable')) return Infinity;
const span = tile.querySelector('.price-line span:first-child, [data-testid="award-price"] span:first-child');
if (!span) return Infinity;
const txt = span.textContent.trim().replace(/,/g, '');
const m = txt.match(/([\d.]+)\s*k?/i);
return m ? parseFloat(m[1]) * (txt.toLowerCase().includes('k') ? 1000 : 1) : Infinity;
}
function applySort() {
const tbody = document.querySelector('table.resultsTable tbody') ||
document.querySelector('tr[data-testid*="matrix-row"]')?.parentElement;
if (!tbody) return;
const rows = [...tbody.querySelectorAll('tr[data-testid*="matrix-row"]')];
if (!sortCol || sortDir === 0) {
rows.sort((a, b) => {
const ai = parseInt(a.dataset.testid?.match(/\d+/)?.[0] || '0');
const bi = parseInt(b.dataset.testid?.match(/\d+/)?.[0] || '0');
return ai - bi;
});
} else {
rows.sort((a, b) => {
const pa = getPoints(a, sortCol), pb = getPoints(b, sortCol);
return sortDir === 1 ? pa - pb : pb - pa;
});
}
rows.forEach(r => tbody.appendChild(r));
}
/* ─── Filter logic ─── */
function applyFilter() {
const rows = document.querySelectorAll('tr[data-testid*="matrix-row"]');
const headers = document.querySelectorAll('.fare-header-container');
const colIds = [...headers].map(h => h.id);
let visible = 0, total = rows.length;
rows.forEach(tr => {
if (activeFilters.size === 0) {
tr.style.display = '';
visible++;
return;
}
const tiles = tr.querySelectorAll('button[data-testid*="valuetile"]');
let show = true;
activeFilters.forEach(colId => {
const idx = colIds.indexOf(colId);
if (idx < 0 || idx >= tiles.length) return;
const tile = tiles[idx];
if (tile.classList.contains('sold-out-button') || tile.textContent.includes('Unavailable')) {
show = false;
}
});
tr.style.display = show ? '' : 'none';
if (show) visible++;
});
const countEl = document.querySelector('p.display-count-text, [class*="display-count"]');
if (countEl) {
countEl.textContent = activeFilters.size > 0
? `Showing ${visible} of ${total} (filtered)`
: `Showing ${total} of ${total}`;
}
const clearBtn = document.querySelector('.as-clear-filters');
if (clearBtn) clearBtn.classList.toggle('hidden', activeFilters.size === 0);
}
/* ═══════════════════════════════════════
─── Calendar ───
═══════════════════════════════════════ */
function cacheShoulderDates(dates) {
dates.forEach(d => {
const ym = d.date.substring(0, 7);
if (!calDateCache[ym]) calDateCache[ym] = {};
calDateCache[ym][d.date] = d;
});
}
async function fetchMonthDates(yearMonth) {
const existing = calDateCache[yearMonth];
if (existing) {
const [y, m] = yearMonth.split('-').map(Number);
const daysInMonth = new Date(y, m, 0).getDate();
if (Object.keys(existing).length >= daysInMonth) return;
}
// Try inline resolve(3) script first (shoulder/calendar dates)
try {
const data = parseResolveData(3);
if (data) {
const dates = data.shoulderDates || data.calendarDates || (Array.isArray(data) ? data : []);
if (dates.length) { cacheShoulderDates(dates); return; }
}
} catch (e) {}
// Fallback: network fetch for the target month
const params = new URLSearchParams(window.location.search);
params.set('OD', yearMonth + '-15');
try {
const resp = await fetch('/search/results/__data.json?' + params.toString());
const text = await resp.text();
const lines = text.split('\n').filter(l => l.trim());
if (!lines[3]) return;
const chunk3 = JSON.parse(lines[3]);
const decoded = devalueRevive(chunk3.data);
const dates = decoded.shoulderDates || decoded.calendarDates || [];
cacheShoulderDates(dates);
} catch (e) {
console.error('AS Enhancer: fetchMonthDates error', e);
}
}
function seedCalCache() {
try {
const sd = document.querySelector('shoulder-dates');
const dates = JSON.parse(sd.getAttribute('dates'));
cacheShoulderDates(dates);
} catch {}
}
function shiftMonth(ym, delta) {
const [y, m] = ym.split('-').map(Number);
const total = (y * 12 + (m - 1)) + delta;
const ny = Math.floor(total / 12);
const nm = (total % 12) + 1;
return ny + '-' + String(nm).padStart(2, '0');
}
function monthLabel(ym) {
const [y, m] = ym.split('-').map(Number);
return new Date(y, m - 1, 1).toLocaleString('en-US', { month: 'long', year: 'numeric' });
}
function priceColor(pts, min, max) {
if (pts == null || pts === 0) return '#e8e8e8';
if (min === max) return '#c6efce';
const ratio = (pts - min) / (max - min);
const r = Math.round(ratio < 0.5 ? ratio * 2 * 255 : 255);
const g = Math.round(ratio < 0.5 ? 255 : (1 - (ratio - 0.5) * 2) * 255);
return 'rgb(' + r + ',' + g + ',60)';
}
function buildMonthGrid(ym, minPts, maxPts) {
const [year, month] = ym.split('-').map(Number);
const daysInMonth = new Date(year, month, 0).getDate();
let startDay = new Date(year, month - 1, 1).getDay();
startDay = startDay === 0 ? 6 : startDay - 1;
const monthData = calDateCache[ym] || {};
const today = new Date().toISOString().split('T')[0];
let html = '<div style="text-align:center;font-weight:600;font-size:13px;margin-bottom:4px">' +
monthLabel(ym) + '</div>';
html += '<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:1px;font-size:11px">';
['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].forEach(d => {
html += '<div style="text-align:center;font-weight:600;padding:2px 0;font-size:10px;color:#666">' + d + '</div>';
});
for (let i = 0; i < startDay; i++) html += '<div></div>';
for (let d = 1; d <= daysInMonth; d++) {
const ds = ym + '-' + String(d).padStart(2, '0');
const info = monthData[ds];
const pts = info ? info.awardPoints : null;
const isPast = ds < today;
const isSel = ds === calSelectedDate;
let bg = '#f5f5f5', fg = '#999', cls = 'as-cal-cell';
if (!isPast && pts != null && pts > 0) {
bg = priceColor(pts, minPts, maxPts); fg = '#000'; cls += ' clickable';
} else if (!isPast && info) {
bg = '#e8e8e8'; fg = '#666'; cls += ' clickable';
} else if (!isPast) {
cls += ' clickable';
}
if (isSel) { bg = '#003366'; fg = '#fff'; }
const ptsLabel = (!isPast && pts > 0) ? (pts >= 1000 ? (pts / 1000) + 'k' : pts) : '';
html += '<div class="' + cls + '" data-date="' + ds + '" style="background:' + bg +
';color:' + fg + ';' + (isPast ? 'opacity:.4;' : '') + '">';
html += '<div style="font-weight:600;font-size:11px">' + d + '</div>';
if (ptsLabel) html += '<div style="font-size:9px">' + ptsLabel + '</div>';
html += '</div>';
}
html += '</div>';
return html;
}
function navigateToDate(dateStr) {
const sd = document.querySelector('shoulder-dates');
if (sd && sd.shadowRoot) {
const btn = sd.shadowRoot.querySelector('button[data-date="' + dateStr + '"]');
if (btn) { btn.click(); return; }
}
const params = new URLSearchParams(window.location.search);
params.set('OD', dateStr);
window.location.href = '/search/results?' + params.toString();
}
function paintCalendar(leftM, rightM) {
const cal = document.getElementById('as-cal');
if (!cal) return;
let allPts = [];
[leftM, rightM].forEach(ym => {
if (calDateCache[ym])
Object.values(calDateCache[ym]).forEach(d => {
if (d.awardPoints > 0) allPts.push(d.awardPoints);
});
});
const minPts = allPts.length ? Math.min(...allPts) : 0;
const maxPts = allPts.length ? Math.max(...allPts) : 0;
cal.innerHTML =
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px"><div>' +
buildMonthGrid(leftM, minPts, maxPts) + '</div><div>' +
buildMonthGrid(rightM, minPts, maxPts) + '</div></div>';
cal.style.opacity = '1';
const lbl = document.getElementById('as-cal-range');
if (lbl) {
const lName = monthLabel(leftM).split(' ')[0];
const rFull = monthLabel(rightM);
lbl.textContent = leftM.split('-')[0] === rightM.split('-')[0]
? lName + ' – ' + rFull
: monthLabel(leftM) + ' – ' + rFull;
}
cal.querySelectorAll('.as-cal-cell[data-date]').forEach(cell => {
cell.addEventListener('click', () => {
const dt = cell.dataset.date;
const today = new Date().toISOString().split('T')[0];
if (dt < today) return;
calSelectedDate = dt;
paintCalendar(calDisplayedMonths[0], calDisplayedMonths[1]);
navigateToDate(dt);
});
});
}
function renderCalendar(leftM, rightM, loading) {
const cal = document.getElementById('as-cal');
if (!cal) return;
calDisplayedMonths = [leftM, rightM];
// Render immediately from cache
paintCalendar(leftM, rightM);
if (loading) cal.style.opacity = '0.5';
// Defer fetches to a macrotask so the browser paints the cached grid first
setTimeout(async () => {
await Promise.all([fetchMonthDates(leftM), fetchMonthDates(rightM)]);
if (calDisplayedMonths[0] === leftM && calDisplayedMonths[1] === rightM) {
paintCalendar(leftM, rightM);
}
}, 0);
}
function buildCalendar() {
calSelectedDate = new URLSearchParams(window.location.search).get('OD') ||
new Date().toISOString().split('T')[0];
seedCalCache();
const old = document.getElementById('as-cal-wrap');
if (old) old.remove();
const wrap = document.createElement('div');
wrap.id = 'as-cal-wrap';
wrap.innerHTML =
'<div id="as-cal-nav">' +
'<button id="as-cal-prev">◀</button>' +
'<span id="as-cal-range"></span>' +
'<button id="as-cal-next">▶</button>' +
'</div>' +
'<div id="as-cal"></div>';
const baggage = document.querySelector('a[href*="baggage"], [class*="baggage"]');
const heading = document.querySelector('h1, h2, [class*="Depart"]');
const insertAfter = baggage?.closest('p, div, span') || heading;
if (insertAfter && insertAfter.parentElement) {
insertAfter.parentElement.insertBefore(wrap, insertAfter.nextSibling);
}
const now = new Date();
const currentYM = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0');
const maxDate = new Date(now.getTime() + CAL_MAX_DAYS * 86400000);
const maxYM = maxDate.getFullYear() + '-' + String(maxDate.getMonth() + 1).padStart(2, '0');
document.getElementById('as-cal-prev').addEventListener('click', () => {
const newLeft = shiftMonth(calDisplayedMonths[0], -1);
if (newLeft < currentYM) return;
renderCalendar(newLeft, calDisplayedMonths[0], true);
});
document.getElementById('as-cal-next').addEventListener('click', () => {
const newRight = shiftMonth(calDisplayedMonths[1], 1);
if (newRight > maxYM) return;
renderCalendar(calDisplayedMonths[1], newRight, true);
});
const selMonth = calSelectedDate.substring(0, 7);
renderCalendar(selMonth, shiftMonth(selMonth, 1), true);
}
/* ─── Cleanup (when switching away from award mode) ─── */
function cleanup() {
document.querySelectorAll('.as-enrich, .as-seg-info, .as-sort-arrow, .as-filter-row, .as-clear-filters').forEach(el => el.remove());
document.querySelectorAll('[data-as-ready]').forEach(el => delete el.dataset.asReady);
const css = document.getElementById('as-enhancer-css');
if (css) css.remove();
const cal = document.getElementById('as-cal-wrap');
if (cal) cal.remove();
const banner = document.getElementById('as-profile-banner');
if (banner) banner.remove();
sortCol = null; sortDir = 0;
activeFilters.clear();
flightData = null;
// Restore hidden rows
document.querySelectorAll('tr[data-testid*="matrix-row"]').forEach(tr => tr.style.display = '');
}
/* ─── Main init ─── */
async function init() {
if (initRunning) return false;
if (!window.location.search.includes('ShoppingMethod=onlineaward')) {
cleanup();
return true; // stop polling — watchNavigation will re-trigger if user switches back
}
const rows = document.querySelectorAll('tr[data-testid*="matrix-row"]');
if (!rows.length) return false;
initRunning = true;
injectStyles();
flightData = await extractFlightData();
const dataRows = flightData?.[0]?.rows || [];
const headerEls = document.querySelectorAll('.fare-header-container');
const colMap = {};
headerEls.forEach((h, i) => { colMap[i] = SOLUTION_PREFIX + h.id; });
setupHeaders();
rows.forEach(tr => {
const card = tr.querySelector('[data-testid*="flight-card"]');
if (!card) return;
const dataRow = matchRowToData(tr, dataRows);
if (!dataRow) return;
enrichSegments(card, dataRow);
const tiles = tr.querySelectorAll('button[data-testid*="valuetile"]');
tiles.forEach((tile, colIdx) => {
const solKey = colMap[colIdx];
const sol = solKey ? dataRow.solutions[solKey] : null;
enrichTile(tile, sol, dataRow.segments);
});
});
sortCol = null; sortDir = 0;
activeFilters.clear();
buildCalendar();
initRunning = false;
return true;
}
/* ─── Poll for results + SPA navigation detection ─── */
function pollForResults() {
const start = Date.now();
const interval = setInterval(async () => {
if ((await init()) || Date.now() - start > POLL_TIMEOUT) clearInterval(interval);
}, POLL_INTERVAL);
}
function watchNavigation() {
let currentUrl = location.href;
const check = () => {
if (location.href !== currentUrl) {
currentUrl = location.href;
setTimeout(() => pollForResults(), 500);
}
};
const origPush = history.pushState;
history.pushState = function () {
origPush.apply(this, arguments);
check();
};
const origReplace = history.replaceState;
history.replaceState = function () {
origReplace.apply(this, arguments);
check();
};
window.addEventListener('popstate', check);
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.addedNodes.length > 0) {
const hasNewRows = [...m.addedNodes].some(n =>
n.nodeType === 1 && (n.matches?.('tr[data-testid*="matrix-row"]') ||
n.querySelector?.('tr[data-testid*="matrix-row"]'))
);
if (hasNewRows) {
setTimeout(() => init(), 300);
break;
}
}
}
});
const table = document.querySelector('table.resultsTable') ||
document.querySelector('tr[data-testid*="matrix-row"]')?.closest('table');
if (table) observer.observe(table, { childList: true, subtree: true });
}
/* ─── Start ─── */
pollForResults();
watchNavigation();
})();
厉害了 zszs
这个怎么提取呢?还要注册帐号?
不用 密码在上面啊
【引用自 zpahai】:
会在AS搜索界面增加一个浮动面板
这个浮动面板在哪里?找不到啊。
随便搜一个里程票 右下角会显示
好用!感谢!
这两个tool是怎么work的,会用到账户的信息去fetch data吗?频繁使用AS会block account或ip吗?
首先AS不需要登录就可以搜票,所以不建议在登录账户的情况使用这个插件
第一个只是修改了显示,并不涉及fetch当前搜索额外的数据
第二个是批量发request,目前AS并不存在rate limit和其他风控
got it thanks
右下角什么也没有的样子……
这个工具看起来很有用啊 不过大佬能简单说明下怎么安装运行这两个脚本文件啊
原来大家都是用脚本的,怪不得等我看到JL的时候黄花菜都凉了
感谢楼主喂饭,试了一下工具一完美运行,工具二原来是需要点这个小飞机图标。再次感谢楼主!
image3338×1428 255 KB
厉害啊厉害
厉害,这是又有大佬要冲钛了
工具二成功了,工具一怎么好像没有运行
好活儿 早日lounge相见
是不是把两个工具分开发表就能有两个升钛贴了该死的PM思维
太好用了大佬
image1512×458 54.4 KB
就是这种票点进去根本没有唉
好用 但好像搜出来的票时区未换算
赞一个! 多谢大牛!
感谢!只有第二个tool的link,第一个的呢 AS enhancer
昨晚要定的AA执飞的欧美商务舱也是幽灵
第一个在楼里 在第二个下面两层
6个的一般都是真幽灵,这个如果不用ba或者点进去看没法验证的
感谢楼主,已点赞。有个小问题,每次用它会从你github仓库更新最新代码是吗?但是我在你git主页没看到这个项目
暂时是private的
装上试了一下,批量搜索那个真的好用!以前找AS里程票要一条一条手搜,现在一下子出来一堆,省了不少时间
太强了大佬,感谢分享!火钳刘明
谢谢,其他几条线路显示一张,点view on Alaska之后也找不到,可能也是幽灵
这个神器会不会很快出现在某星球上
一张说明是搜到到显示结果之间已经被人hold了
【引用自 Ava.太太太后】:
程票要一条一条手搜,现在一下子出来一堆,省了不少时间
确实是,我刚刚试着用,发现一张JL 75k,我还点进去验证了。过了差不过5分钟再看已经被人拿走了
某星球现在已经饥不择食了
连听课房、付费买会员都要强力推荐
感谢分享。
嗯嗯,谢谢大佬
是我安装姿势不对吗,我装了后没有反应
image2606×1406 346 KB
我大概懂了,是不是tampermonkey设置没有开allow user script?我后面会改成github/gist应该就没问题了,chrome浏览器插件developer mode也开一下,就是右上角的
image2879×1462 219 KB
给大佬冲完钛之后有一点点想挪到白金了… 这个工具给某星球和黄牛免费拿走之后一般人更是抢不到票了…
顺便送一些availability吧
2026-04-08 | AS Business · 95K · $5.60 · 1s · HA823
2026-08-16 | AS Business · 95K · $24.80 · 7s · AS823
2026-08-23 | AS Business · 95K · $24.80 · 7s · AS823
2026-08-29 | AS Business · 95K · $24.80 · 4s · AS823
2026-03-25 | AS Business · 75K · $55.63 · 6s · JL2
2026-03-31 | AS Business · 75K · $55.63 · 6s · JL2
2027-01-31 | AS Business · 75K · $55.63 · 6s · JL2
2026-03-26 | AS Business · 95K · $48.73 · 1s · AA170
2027-02-11 | AS Business · 75K · $55.63 · 6s · JL16
2026-08-01 | AS Business · 95K · $27.40 · 9s · JX11
2026-10-19 | AS Business · 95K · $27.40 · 9s · JX11
2026-07-17 | AS Business · 95K · $58.53 · 9s · JX2
2026-09-16 | AS Business · 75K · $46.43 · 1s · JX2
2026-04-17 | AS Business · 60K · $56.93 · 1s · HA824
2026-07-26 | AS Business · 60K · $56.93 · 3s · JL68, AS824
2026-06-01 | AS Business · 75K · $55.63 · 6s · JL12
2026-09-16 | AS Business · 85K · $52.03 · 9s · JX32
2026-09-16 | AS Business · 75K · $52.03 · 9s · JX12
2026-04-27 | AS Business · 110K · $55.63
同意zszs
给大牛点赞。感觉以后票更难抢了
我怎么看不到小飞机了 就看到过一次
等稳定了再回来看看
谢谢!开了allow user scripts后就能看到小飞机了!
这个搜索界面怎么弄出来?还是自动会跳出来?
看到了。感谢!
手动点赞先
是不是改的时候把第一个tool AS enhancer不小心删掉了,还是没找到link啊
点赞慢慢看
厉害了
【引用自 未知】:
[AS]里程票搜索工具和结果增强显示 — AS Enhancer + Batch Search 航空常旅客
AS Enhancer
lz 太厉害了!支持冲钛
要不要在code里加一些验证泥潭登陆等级的东西?现在这样确实太容易被票贩子/xhs/一些别有用心的人转载出去了。不过还是感谢lz,搓出这样一个工具方便谭友太伟大了
Screenshot 2026-03-21 at 5.19.18 PM1452×372 32.4 KB
好像很厉害
感谢大佬!
nb 了
怎么样apply code?
感谢大佬分享! 请教一下,AS Enhancer日历貌似只显示经济舱的价格?
卧槽我刚想vibe一个这个冲钛 钛金还是太快了
楼主也是刚变钛
我前两天刚vibe了一个 懒得登出AS账号就没试
今天发现lz已经冲钛了
泥潭高手云集
AS应该是最简单的了
直接新建脚本 粘贴到油猴插件里面就行
这个是match原来的AS平铺日期显示逻辑的显示的是当天最低价,我可以看看有没有所有数据,有的话可以加一个开关
感谢大佬!!
各位群友能给个日期,from 和 to么
我开了incognito,只enable Tamper Monkey和两个script,还是看不见小飞机啊
或者chrome版本给一个?
我用的 sea tpe points Apr 19
【引用自 未知】:
[AS]里程票搜索工具和结果增强显示 — AS Enhancer + Batch Search 航空常旅客
我大概懂了,是不是tampermonkey设置没有开allow user script?我后面会改成github/gist应该就没问题了,chrome浏览器插件developer mode也开一下,就是右上角的
[image]
大概率是这个问题?因为不是从链接直接安装而是手动所以算作user script?
确实是我的问题,为了防止中奖,特意新注册的Profile
顺带再提个建议吧,我记得response里有 多少EQM和Earned miles,都显示舱位了,其实这个也可以扔扔上去
image1222×518 30 KB
笑死,全美没票
这个我看了并没有这俩field,可能需要到下一步才会有
牛逼~
不过当然商务舱都被抢没了。。看到的QR(原是我痴心妄想)和AA商务舱都是幽灵
已严肃修复
目前加了登录验证和混淆,只要不让外面用就行
已经钛金了
点赞,各仓位都剩6的票可以过滤掉或者标一下,大概率是幽灵
提个issue,第一个脚本貌似搜索的时候在现金和里程票之间切换后日历视图会显示错误的数值
我有空看看吧
OneworldSaver Pro — 3/24/2026
2027-01-13 | AA Business · 60K · $44.43
2027-01-14 | AA Business · 60K · $44.43
2027-01-15 | AA Business · 60K · $44.43
2027-01-17 | AA Business · 60K · $44.43
2027-01-18 | AA Business · 60K · $44.43
2026-07-21 | AA Business · 60K · $44.43
2027-01-13 | AA Business · 60K · $44.43
2027-01-14 | AA Business · 60K · $44.43
2027-01-15 | AA Business · 60K · $44.43
2027-01-18 | AA Business · 60K · $44.43
2027-01-13 | AA Business · 60K · $44.43
2027-01-14 | AA Business · 60K · $44.43
2027-01-15 | AA Business · 60K · $44.43
2027-01-18 | AA Business · 60K · $44.43
2026-03-24 | AA Business · 60K · $11.20
2026-03-25 | AA Business · 60K · $11.20
2026-04-17 | AA Business · 60K · $11.20
2026-08-25 | AA First · 80K · $5.60
2026-08-25 | AA First · 80K · $5.60
2026-04-04 | AA First · 80K · $5.60
2026-04-04 | AA First · 80K · $5.60
2026-08-26 | AA Business · 60K · $5.60
2026-08-26 | AA Business · 60K · $5.60
2026-08-26 | AA Business · 60K · $5.60
2026-08-26 | AA Business · 60K · $5.60
2026-08-26 | AA Business · 60K · $5.60
小飞机那个标突然没了,请问batch search如何debug?
2026-11-08 | AS Business · 75K · $51.93 · 1s · JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32
2026-12-19 | AS Business · 75K · $51.93 · 1s · JX12
2026-11-08 | AS Business · 75K · $51.93 · 1s · JX32, JX32, JX32, JX32, JX32, JX32
2026-12-19 | AS Business · 75K · $46.33 · 1s · JX12
2026-11-08 | AS Business · 75K · $51.93 · 1s · JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32
2026-12-19 | AS Business · 75K · $51.93 · 1s · JX12
2027-02-11 | AS Business · 95K · $18.10 · 6s · AS626, AS247, AS731, AS732, AS743
2027-02-12 | AS Business · 95K · $18.10 · 6s · AS1489, AS726
2026-10-16 | AS Business · 110K · $62.53 · 1s · JL18, JL18, JL18
2026-09-08 | AS Business · 75K · $55.53 · 6s · JL6
2026-11-08 | AS Business · 85K · $51.93 · 1s · JX32, JX32, JX32
2026-11-08 | AS Business · 75K · $46.33 · 1s · JX32
2026-11-08 | AS Business · 85K · $51.93 · 1s · JX32, JX32, JX32, JX32
2026-11-08 | AS Business · 85K · $51.93 · 1s · JX32, JX32, JX32
2026-11-08 | AS Business · 85K · $51.93 · 1s · JX32, JX32, JX32, JX32, JX32, JX32, JX32, JX32
2026-05-19 | AA Business · 60K · $44.43 · 1s · JL242 → AS824, JL240 → AS824
2026-05-20 | AA Business · 60K · $44.43 · 1s · JL236 → AS824, JL234 → AS824, JL232 → AS824
2026-06-10 | AA Business · 60K · $44.43 · 1s · JL236 → AS824, JL234 → AS824, JL232 → AS824
2026-06-25 | AA Business · 60K · $44.43 · 1s · JL242 → JL68, JL240 → JL68
2026-06-26 | AA Business · 60K · $44.43 · 1s · JL236 → JL68, JL234 → JL68, JL232 → JL68
2027-01-13 | AA Business · 60K · $44.43 · 1s · JL242 → JL68, JL240 → JL68
2027-01-14 | AA Business · 60K · $44.43 · 2s · JL236 → JL68, JL234 → JL68, JL232 → JL68, JL242 → JL68, JL240 → JL68
2027-01-17 | AA Business · 60K · $44.43 · 1s · JL240 → JL68
2027-01-18 | AA Business · 60K · $44.43 · 1s · JL236 → JL68, JL234 → JL68, JL232 → JL68
2026-05-19 | AA Business · 60K · $44.43 · 1s · JL464 → AS824, JL462 → AS824
2026-05-20 | AA Business · 60K · $44.43 · 1s · JL456 → AS824, JL454 → AS824
2026-06-09 | AA Business · 60K · $44.43 · 1s · JL464 → AS824
2026-06-10 | AA Business · 60K · $44.43 · 1s · JL456 → AS824, JL454 → AS824
2026-06-25 | AA Business · 60K · $44.43 · 1s · JL464 → JL68
2026-06-26 | AA Business · 60K · $44.43 · 1s · JL454 → JL68
2027-01-13 | AA Business · 60K · $44.43 · 1s · JL464 → JL68
2027-01-14 | AA Business · 60K · $44.43 · 1s · JL456 → JL68, JL454 → JL68
2027-01-18 | AA Business · 60K · $44.43 · 1s · JL456 → JL68, JL454 → JL68
2026-05-19 | AA Business · 60K · $44.43 · 1s · JL486 → AS824
2026-05-20 | AA Business · 60K · $44.43 · 1s · JL478 → AS824, JL476 → AS824, JL474 → AS824
2026-06-25 | AA Business · 60K · $44.43 · 1s · JL486 → JL68, JL484 → JL68
2026-06-26 | AA Business · 60K · $44.43 · 1s · JL480 → JL68, JL478 → JL68, JL476 → JL68, JL474 → JL68
2026-07-21 | AA Business · 60K · $44.43 · 1s · JL474 → JL68
2027-01-14 | AA Business · 60K · $44.43 · 1s · JL480 → JL68, JL478 → JL68, JL476 → JL68, JL474 → JL68
2027-01-18 | AA Business · 60K · $44.43 · 1s · JL478 → JL68, JL476 → JL68, JL474 → JL68
2026-05-19 | AA Business · 60K · $44.43 · 1s · JL332 → AS824, JL330 → AS824
2026-05-20 | AA Business · 60K · $44.43 · 1s · JL312 → AS824, JL310 → AS824, JL304 → AS824, JL302 → AS824, JL300 → AS824
2026-06-09 | AA Business · 60K · $44.43 · 1s · JL332 → AS824, JL330 → AS824, JL328 → AS824, JL322 → AS824
2026-06-10 | AA Business · 60K · $44.43 · 1s · JL312 → AS824, JL310 → AS824, JL304 → AS824, JL302 → AS824, JL300 → AS824
2026-04-05 | AA Business · 60K · $44.43 · 1s · JL922 → JL68, JL920 → JL68, JL916 → JL68, JL914 → JL68
2026-05-19 | AA Business · 60K · $44.43 · 1s · JL922 → AS824, JL920 → AS824, JL918 → AS824, JL914 → AS824
2026-05-20 | AA Business · 60K · $44.43 · 1s · JL906 → AS824, JL904 → AS824, JL902 → AS824, JL900 → AS824
2026-04-29 | AA Business · 60K · $11.20 · 2s · AA1996 → JL69, AA3317 → JL69, AA1999 → JL69, AA711 → JL69
2027-01-08 | AA Business · 60K · $11.20 · 2s · AA1247 → JL69, AA2900 → JL69, AA2793 → JL69
2026-05-19 | AA Business · 60K · $44.43 · 1s · JL530 → AS824, JL528 → AS824, JL526 → AS824
2026-05-20 | AA Business · 60K · $44.43 · 1s · JL508 → AS824, JL500 → AS824
2026-04-21 | AA Business · 60K · $11.20 · 1s · AS489 → JL69
2026-03-26 | AA Business · 60K · $53.03 · 1s · JL60 → AA3176
2026-03-26 | AA Business · 60K · $47.43 · 2s · JL60
2026-04-17 | AA Business · 60K · $11.20 · 2s · AA300 → JL69
2026-03-26 | AA Business · 60K · $44.43 · 1s · JL62
2026-04-12 | AA Business · 60K · $5.60 · 2s · JL67 → JL241, JL67 → JL237
2026-05-12 | AA Business · 60K · $5.60 · 1s · AS823 → JL101
2026-03-27 | AA Business · 60K · $43.13 · 1s · JL300 → JL2
2026-10-18 | AS Business · 60K · $56.93 · 1s · JL18, JL18, JL18
2026-04-10 | AS Business · 60K · $18.10 · 2s · AS2110
OneworldSaver Pro — 3/26/2026
2026-06-04 | AA First · 80K · $43.13 · 1s · JL332 → JL10, JL330 → JL10, JL328 → JL10, JL326 → JL10, JL322 → JL10, JL320 → JL10, JL318 → JL10, JL316 → JL10, JL314 → JL10, JL312 → JL10
2026-10-15 | AA First · 80K · $43.13 · 1s · JL332 → JL10, JL330 → JL10, JL328 → JL10, JL326 → JL10, JL322 → JL10, JL320 → JL10, JL318 → JL10, JL316 → JL10, JL314 → JL10, JL312 → JL10
2027-02-18 | AA First · 80K · $43.13 · 2s · JL332 → JL10, JL330 → JL10, JL328 → JL10, JL326 → JL10, JL324 → JL10, JL322 → JL10, JL320 → JL10, JL318 → JL10, JL316 → JL10, JL314 → JL10, JL312 → JL10, JL310 → JL10, JL308 → JL10, JL306 → JL10
2027-02-20 | AA First · 80K · $43.13 · 2s · JL302 → JL10, JL300 → JL10, JL312 → JL56, JL310 → JL56, JL308 → JL56, JL306 → JL56, JL304 → JL56, JL302 → JL56, JL300 → JL56
2027-02-18 | AA First · 80K · $43.13 · 1s · JL228 → JL10, JL224 → JL10
2027-02-19 | AA First · 80K · $44.43 · 2s · JL228 → JL56
2027-02-20 | AA First · 80K · $43.13 · 2s · JL220 → JL10, JL220 → JL56
2026-06-04 | AA First · 80K · $43.13 · 1s · JL138 → JL10, JL130 → JL10, JL128 → JL10, JL126 → JL10, JL124 → JL10, JL118 → JL10, JL116 → JL10
2026-10-15 | AA First · 80K · $43.13 · 1s · JL116 → JL10
2027-02-18 | AA First · 80K · $43.13 · 2s · JL138 → JL10, JL130 → JL10, JL128 → JL10, JL126 → JL10, JL124 → JL10, JL3006 → JL10, JL118 → JL10, JL116 → JL10, JL114 → JL10
2026-05-25 | AA First · 80K · $5.60 · 1s · JL9
2027-01-21 | AA First · 80K · $5.60 · 1s · JL9
2027-02-04 | AA First · 80K · $5.60 · 1s · JL9
2026-04-07 | AA First · 80K · $5.60 · 1s · JL5 → JL901, JL5 → JL903, JL5 → JL905, JL5 → JL907, JL5 → JL909
2027-02-18 | AA First · 80K · $5.60 · 2s · JL5 → JL915, JL5 → JL919, JL5 → JL921
2027-02-19 | AA First · 80K · $5.60 · 2s · JL5 → JL925, JL3 → JL915, JL3 → JL919, JL3 → JL921, JL3 → JL925
2026-06-18 | AA First · 80K · $43.13 · 1s · JL524 → JL2, JL522 → JL2, JL518 → JL2, JL516 → JL2
2026-10-15 | AA First · 80K · $43.13 · 1s · JL530 → JL2, JL528 → JL2, JL526 → JL2, JL524 → JL2, JL522 → JL2, JL518 → JL2
2027-02-18 | AA First · 80K · $5.60 · 2s · JL9 → JL303, JL9 → JL305, JL9 → JL307, JL9 → JL311, JL9 → JL313, JL9 → JL315, JL9 → JL317, JL55 → JL303, JL9 → JL321, JL55 → JL305, JL55 → JL307, JL9 → JL325, JL55 → JL311, JL9 → JL327, JL55 → JL313, JL9 → JL329, JL55 → JL315, JL9 → JL331, JL55 → JL317, JL9 → JL333, JL9 → JL335, JL55 → JL321
2027-02-19 | AA First · 80K · $5.60 · 2s · JL55 → JL329, JL55 → JL331, JL55 → JL333, JL55 → JL335
2026-10-15 | AA First · 80K · $43.13 · 1s · JL918 → JL10, JL916 → JL10, JL904 → JL10
2027-02-18 | AA First · 80K · $43.13 · 2s · JL922 → JL10, JL920 → JL10, JL918 → JL10, JL916 → JL10, JL914 → JL10, JL988 → JL10, JL912 → JL10, JL910 → JL10, JL908 → JL10, JL906 → JL10, JL904 → JL10, JL902 → JL10
2027-02-18 | AA First · 80K · $5.60 · 2s · JL9 → JL915, JL9 → JL919, JL9 → JL921, JL55 → JL915, JL9 → JL925
2027-02-19 | AA First · 80K · $5.60 · 2s · JL55 → JL925
2026-06-04 | AA First · 80K · $43.13 · 1s · JL530 → JL10, JL528 → JL10, JL526 → JL10, JL524 → JL10, JL522 → JL10, JL518 → JL10, JL516 → JL10, JL514 → JL10, JL512 → JL10, JL510 → JL10, JL508 → JL10
2027-02-18 | AA First · 80K · $43.13 · 1s · JL530 → JL10, JL528 → JL10, JL526 → JL10, JL524 → JL10, JL522 → JL10, JL520 → JL10, JL518 → JL10, JL516 → JL10, JL514 → JL10, JL512 → JL10, JL510 → JL10, JL508 → JL10, JL506 → JL10, JL504 → JL10
2027-02-18 | AA First · 80K · $5.60 · 2s · JL9 → JL101, JL9 → JL103, JL9 → JL111, JL9 → JL113, JL9 → JL115, JL9 → JL119, JL55 → JL101, JL9 → JL121, JL55 → JL103, JL9 → JL125, JL9 → JL127, JL55 → JL111, JL55 → JL113, JL9 → JL133, JL9 → JL3009, JL55 → JL115, JL9 → JL137, JL9 → JL139, JL55 → JL119, JL55 → JL121, JL55 → JL125
2027-02-19 | AA First · 80K · $5.60 · 2s · JL55 → JL133, JL55 → JL3009, JL55 → JL137, JL55 → JL139
2027-02-18 | AA First · 80K · $5.60 · 2s · JL15 → JL303, JL15 → JL305, JL15 → JL307, JL15 → JL311, JL15 → JL313, JL15 → JL315, JL15 → JL317, JL15 → JL321, JL15 → JL325, JL15 → JL327
2027-02-19 | AA First · 80K · $5.60 · 1s · JL15 → JL333, JL15 → JL335
2026-04-29 | AA Business · 80K · $11.20 · 2s · AA1996 → JL69, AA3317 → JL69, AA1999 → JL69, AA711 → JL69
2027-01-08 | AA Business · 80K · $11.20 · 2s · AA1247 → JL69, AA2900 → JL69, AA2793 → JL69
2027-02-18 | AA First · 80K · $5.60 · 2s · JL15 → JL915, JL15 → JL919, JL15 → JL921
2027-02-19 | AA First · 80K · $5.60 · 1s · JL15 → JL925
2027-02-18 | AA First · 80K · $5.60 · 2s · JL11 → JL501, JL11 → JL505, JL11 → JL515, JL11 → JL517
2027-02-19 | AA First · 80K · $5.60 · 2s · JL11 → JL525, JL11 → JL527, JL11 → JL529, JL11 → JL531
2027-02-19 | AA First · 80K · $43.13 · 2s · JL138 → JL12, JL130 → JL12, JL128 → JL12, JL126 → JL12, JL124 → JL12, JL3006 → JL12, JL118 → JL12, JL116 → JL12, JL114 → JL12
2027-02-20 | AA First · 80K · $43.13 · 2s · JL104 → JL12, JL102 → JL12
2027-02-19 | AA First · 80K · $43.13 · 2s · JL902 → JL6
2027-02-20 | AA First · 80K · $43.13 · 2s · JL910 → JL4, JL908 → JL4, JL906 → JL4, JL904 → JL4, JL902 → JL4
2027-02-18 | AA First · 80K · $5.60 · 2s · JL5 → JL101, JL5 → JL103, JL5 → JL111, JL5 → JL113, JL5 → JL115, JL5 → JL119, JL5 → JL121, JL5 → JL125, JL5 → JL127
2027-02-19 | AA First · 80K · $5.60 · 2s · JL3 → JL101, JL5 → JL137, JL5 → JL139, JL3 → JL103, JL3 → JL111, JL3 → JL113, JL3 → JL115, JL3 → JL119, JL3 → JL121, JL3 → JL125, JL3 → JL127, JL3 → JL133, JL3 → JL3009, JL3 → JL137, JL3 → JL139
2026-05-19 | AA Business · 80K · $44.43 · 1s · JL524 → AS824, JL522 → AS824
2026-05-20 | AA Business · 80K · $44.43 · 1s · JL506 → AS824, JL504 → AS824, JL502 → AS824
2026-06-18 | AA First · 80K · $43.13 · 1s · JL138 → JL2, JL130 → JL2, JL128 → JL2, JL126 → JL2, JL124 → JL2
2027-02-18 | AA First · 80K · $43.13 · 2s · JL464 → JL10, JL462 → JL10, JL460 → JL10, JL456 → JL10
2026-03-26 | AA First · 80K · $53.03 · 3s · JL60 → AA12, JL60 → AA394
2026-04-21 | AA Business · 80K · $11.20 · 1s · AS489 → JL69
2027-02-18 | AA First · 80K · $5.60 · 2s · JL9 → JL501, JL9 → JL505, JL9 → JL515, JL9 → JL517, JL55 → JL501, JL55 → JL505, JL9 → JL525, JL9 → JL527, JL9 → JL529
2026-04-17 | AA Business · 80K · $11.20 · 2s · AA300 → JL69
2027-02-20 | AA First · 80K · $43.13 · 2s · JL910 → JL16, JL908 → JL16, JL906 → JL16, JL904 → JL16, JL902 → JL16
2027-02-18 | AA First · 80K · $5.60 · 2s · JL15 → JL101, JL15 → JL103, JL15 → JL111, JL15 → JL113, JL15 → JL121, JL15 → JL125
2027-02-20 | AA First · 80K · $43.13 · 2s · JL302 → JL12, JL300 → JL12
2027-02-18 | AA First · 80K · $5.60 · 2s · JL11 → JL303, JL11 → JL305, JL11 → JL307, JL11 → JL311, JL11 → JL313, JL11 → JL315
2027-02-18 | AA First · 80K · $5.60 · 2s · JL11 → JL915, JL11 → JL919
2027-02-20 | AA First · 80K · $43.13 · 2s · JL500 → JL12
2027-02-18 | AA First · 80K · $5.60 · 2s · JL11 → JL111, JL11 → JL113, JL11 → JL115, JL11 → JL119, JL11 → JL121, JL11 → JL125
2027-02-20 | AA First · 80K · $43.13 · 2s · JL322 → JL4, JL302 → JL6, JL320 → JL4, JL300 → JL6, JL318 → JL4, JL316 → JL4, JL314 → JL4, JL312 → JL4, JL310 → JL4, JL308 → JL4, JL306 → JL4, JL304 → JL4, JL300 → JL4
2027-02-19 | AA First · 80K · $5.60 · 2s · JL3 → JL311, JL3 → JL313, JL3 → JL315, JL3 → JL317, JL3 → JL321, JL3 → JL325, JL3 → JL327, JL3 → JL329, JL3 → JL331, JL3 → JL333, JL3 → JL335
2026-04-07 | AA First · 80K · $5.60 · 1s · JL5 → JL531, JL5 → JL501, JL5 → JL503, JL5 → JL507
2027-02-19 | AA First · 80K · $43.13 · 2s · JL138 → JL6, JL130 → JL6, JL128 → JL6, JL126 → JL6, JL124 → JL6, JL3006 → JL6, JL118 → JL6, JL116 → JL6, JL114 → JL6, JL138 → JL4
2026-06-18 | AA First · 80K · $43.13 · 1s · JL332 → JL2, JL330 → JL2, JL328 → JL2, JL326 → JL2, JL322 → JL2, JL320 → JL2, JL318 → JL2
感覺不少幽靈啊
已经简单filter过一次了,不能保证远期没幽灵,但是我测试下来一半一半吧,至少能到填信息的一步
最近JL抽风太多 昨天差点都炸了 每个日期都有幽灵票
大佬,请问填完信息点确定显示无法confirm flight就是所谓的幽灵票吗
最后一步付款/出票时报错就是
太赞了!确实我的allow user scripts就能看到了!
不知道为什么batch searcher突然不进行搜索了,已经登陆泥潭
AS Batch award search, 鼠标hover 要是能显示还剩下几个位置就更好了
可能主页不一定能获取到cookie,手动搜一张票再试试
更新了,有的没的都全加上了
改版后好像有bug了, 点不开航班 如果只有一班, 只能手动再搜
确实最后一行有bug,在修了
大佬辛苦
修好了,应该更新就好了
感谢lz让我我感受到了来自泥潭的温暖