|
|
/**
|
|
|
* Browser 执行脚本(纯字符串)。禁止改为 function。
|
|
|
*/
|
|
|
|
|
|
export const SCROLL_WIDGET_INTO_VIEW =
|
|
|
"(el) => { el.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'instant' }); }";
|
|
|
|
|
|
export const IS_SELECT_TAG = "(el) => el.tagName === 'SELECT'";
|
|
|
|
|
|
/** @deprecated Network-First 禁止 DOM 报价提取 */
|
|
|
export const COLLECT_EMBEDDED_JSON = `() => {
|
|
|
const payloads = [];
|
|
|
const next = document.getElementById("__NEXT_DATA__");
|
|
|
if (next && next.textContent) {
|
|
|
try { payloads.push(JSON.parse(next.textContent)); } catch (e) {}
|
|
|
}
|
|
|
document.querySelectorAll('script[type="application/json"]').forEach((el) => {
|
|
|
const text = el.textContent ? el.textContent.trim() : "";
|
|
|
if (!text) return;
|
|
|
try { payloads.push(JSON.parse(text)); } catch (e) {}
|
|
|
});
|
|
|
return payloads;
|
|
|
}`;
|
|
|
|
|
|
/** @deprecated Network-First 禁止 DOM 报价提取 */
|
|
|
export const COLLECT_CLIENT_STATE = `() => {
|
|
|
const payloads = [];
|
|
|
const next = document.getElementById("__NEXT_DATA__");
|
|
|
if (next && next.textContent) {
|
|
|
try { payloads.push(JSON.parse(next.textContent)); } catch (e) {}
|
|
|
}
|
|
|
for (const key of Object.keys(window)) {
|
|
|
if (!/quote|rate|pricing|apollo|relay|__NEXT/i.test(key)) continue;
|
|
|
const val = window[key];
|
|
|
if (val && typeof val === "object") payloads.push(val);
|
|
|
}
|
|
|
return payloads;
|
|
|
}`;
|
|
|
|
|
|
export const COLLECT_ADDRESS_LABELS_FROM_DOM = `({ streetNumber, cityHint }) => {
|
|
|
const results = [];
|
|
|
const seen = new Set();
|
|
|
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
|
let node = walker.nextNode();
|
|
|
while (node) {
|
|
|
const chunk = (node.textContent || "").trim();
|
|
|
const hitStreet = streetNumber && chunk.includes(streetNumber);
|
|
|
const hitCity = cityHint.length >= 3 && chunk.toLowerCase().includes(cityHint.toLowerCase());
|
|
|
if (!hitStreet && !hitCity) { node = walker.nextNode(); continue; }
|
|
|
let el = node.parentElement;
|
|
|
for (let depth = 0; depth < 8 && el; depth += 1) {
|
|
|
const tag = el.tagName.toLowerCase();
|
|
|
if (tag === "input" || tag === "textarea") break;
|
|
|
const rowText = (el.innerText || "").replace(/\\s+/g, " ").trim();
|
|
|
if (rowText.length >= 8 && rowText.length <= 180 && (!streetNumber || rowText.includes(streetNumber))) {
|
|
|
if (!seen.has(rowText)) { seen.add(rowText); results.push(rowText); }
|
|
|
break;
|
|
|
}
|
|
|
el = el.parentElement;
|
|
|
}
|
|
|
node = walker.nextNode();
|
|
|
}
|
|
|
return results;
|
|
|
}`;
|
|
|
|
|
|
/** Google Places pac-item:主行 + 副行,避免 innerText 粘连导致 suburb 丢失 */
|
|
|
export const COLLECT_PAC_ITEM_LABELS = `() => {
|
|
|
const results = [];
|
|
|
const seen = new Set();
|
|
|
document.querySelectorAll(".pac-container .pac-item").forEach((item) => {
|
|
|
const main =
|
|
|
item.querySelector(".pac-item-query")?.textContent?.replace(/\\s+/g, " ").trim() ||
|
|
|
"";
|
|
|
const secondary =
|
|
|
item.querySelector(".pac-item-secondary")?.textContent?.replace(/\\s+/g, " ").trim() ||
|
|
|
"";
|
|
|
let label = "";
|
|
|
if (main && secondary) {
|
|
|
label = main + "|" + secondary;
|
|
|
} else {
|
|
|
label = (item.innerText || "").replace(/\\s+/g, " ").trim();
|
|
|
}
|
|
|
if (label.length >= 8 && !seen.has(label)) {
|
|
|
seen.add(label);
|
|
|
results.push(label);
|
|
|
}
|
|
|
});
|
|
|
return results;
|
|
|
}`;
|
|
|
|
|
|
/** MotherShip widget listbox + pac + styled-components:主行|副行(对齐图2/图3 多候选) */
|
|
|
export const COLLECT_WIDGET_SUGGESTION_LABELS = `(function() {
|
|
|
var skipRe = /use my location|使用我的位置|get a freight|freight details|pick up from|deliver to|where to/i;
|
|
|
var stateRe = /(USA|US|CA|NY|TX|FL|IL|PA|OH|GA|NC|MI|NJ|VA|WA|AZ|MA|TN|IN|MO|MD|WI|CO|MN|SC|AL|LA|KY|OR|OK|CT|UT|IA|NV|AR|MS|KS|NM|NE|WV|ID|HI|NH|ME|MT|RI|DE|SD|ND|AK|DC|VT|WY|PR)/;
|
|
|
var seen = new Set();
|
|
|
var results = [];
|
|
|
|
|
|
function push(label) {
|
|
|
var t = (label || "").replace(/\\s+/g, " ").trim();
|
|
|
if (t.length < 8 || t.length > 180 || skipRe.test(t) || seen.has(t)) return;
|
|
|
seen.add(t);
|
|
|
results.push(t);
|
|
|
}
|
|
|
|
|
|
function pushLines(el) {
|
|
|
var raw = (el.innerText || "").replace(/\\r/g, "");
|
|
|
var lines = raw.split("\\n").map(function(l) { return l.trim(); }).filter(Boolean);
|
|
|
if (!lines.length || lines.some(function(l) { return skipRe.test(l); })) return;
|
|
|
if (lines.length >= 2) {
|
|
|
push(lines[0] + "|" + lines.slice(1).join(", "));
|
|
|
} else {
|
|
|
push(lines[0]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 1. ARIA listbox/option(标准)
|
|
|
document.querySelectorAll("#react-quoter-landing-plugin [role='listbox'] [role='option']").forEach(pushLines);
|
|
|
|
|
|
// 2. Google Places pac-container(标准)
|
|
|
document.querySelectorAll(".pac-container .pac-item").forEach(function(item) {
|
|
|
var mainEl = item.querySelector(".pac-item-query");
|
|
|
var secondaryEl = item.querySelector(".pac-item-secondary");
|
|
|
var main = mainEl && mainEl.textContent ? mainEl.textContent.replace(/\\s+/g, " ").trim() : "";
|
|
|
var secondary = secondaryEl && secondaryEl.textContent ? secondaryEl.textContent.replace(/\\s+/g, " ").trim() : "";
|
|
|
if (main && secondary) {
|
|
|
push(main + "|" + secondary);
|
|
|
} else {
|
|
|
pushLines(item);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 3. Styled-components 地址联想(MotherShip 自定义)
|
|
|
if (results.length === 0) {
|
|
|
var candidateTexts = new Set();
|
|
|
document.querySelectorAll("div[class*='sc-']").forEach(function(el) {
|
|
|
var text = (el.innerText || "").replace(/\\s+/g, " ").trim();
|
|
|
if (text.length >= 15 && text.length <= 150 && text.indexOf(",") >= 0 && stateRe.test(text) && !skipRe.test(text)) {
|
|
|
var rect = el.getBoundingClientRect();
|
|
|
if (rect.width > 180 && rect.height >= 24 && rect.height <= 100) {
|
|
|
if (!candidateTexts.has(text)) {
|
|
|
candidateTexts.add(text);
|
|
|
push(text);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 4. 全局 [role="listbox"] 作为后备
|
|
|
if (results.length === 0) {
|
|
|
document.querySelectorAll('[role="listbox"]:not(#react-quoter-landing-plugin *)').forEach(function(listbox) {
|
|
|
listbox.querySelectorAll('[role="option"], [role="menuitem"], li').forEach(pushLines);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
return results;
|
|
|
})()`;
|
|
|
|
|
|
/** 输入框下方联想行坐标:供 page.mouse.click 使用(不依赖 ARIA listbox) */
|
|
|
export const COLLECT_SUGGESTION_MOUSE_TARGETS = `(input) => {
|
|
|
if (!input) return [];
|
|
|
var inputRect = input.getBoundingClientRect();
|
|
|
var query = (input.value || "").replace(/\\s+/g, " ").trim();
|
|
|
var streetNum = (query.match(/^\\d+/) || [])[0] || "";
|
|
|
var skipRe = /use my location|使用我的位置|pick up from|deliver to|where to|freight details|search by address|搜索地址|get a freight/i;
|
|
|
var results = [];
|
|
|
var seen = new Set();
|
|
|
|
|
|
function add(el) {
|
|
|
if (!el || el === input) return;
|
|
|
var rect = el.getBoundingClientRect();
|
|
|
if (!rect.width || !rect.height) return;
|
|
|
var text = (el.innerText || "").replace(/\\s+/g, " ").trim();
|
|
|
if (text.length < 8 || text.length > 200 || skipRe.test(text)) return;
|
|
|
if (streetNum && text.indexOf(streetNum) < 0) return;
|
|
|
if (rect.top < inputRect.top + inputRect.height * 0.4) return;
|
|
|
if (rect.top > inputRect.bottom + 380) return;
|
|
|
if (rect.width < 100 || rect.width > 900) return;
|
|
|
if (rect.height < 18 || rect.height > 110) return;
|
|
|
var childCount = el.children ? el.children.length : 0;
|
|
|
if (childCount > 8) return;
|
|
|
var key = Math.round(rect.top) + "|" + Math.round(rect.left) + "|" + text.slice(0, 32);
|
|
|
if (seen.has(key)) return;
|
|
|
seen.add(key);
|
|
|
results.push({
|
|
|
x: rect.left + rect.width / 2,
|
|
|
y: rect.top + rect.height / 2,
|
|
|
width: rect.width,
|
|
|
height: rect.height,
|
|
|
text: text,
|
|
|
top: rect.top,
|
|
|
area: rect.width * rect.height,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
document.querySelectorAll('div[class*="sc-"], span[class*="sc-"], li, [role="option"], .pac-item').forEach(add);
|
|
|
|
|
|
results.sort(function (a, b) {
|
|
|
if (a.top !== b.top) return a.top - b.top;
|
|
|
return a.area - b.area;
|
|
|
});
|
|
|
|
|
|
var filtered = [];
|
|
|
for (var i = 0; i < results.length; i++) {
|
|
|
var r = results[i];
|
|
|
var dup = filtered.some(function (f) {
|
|
|
return Math.abs(f.top - r.top) < 10 && Math.abs(f.x - r.x) < 60;
|
|
|
});
|
|
|
if (!dup) filtered.push(r);
|
|
|
}
|
|
|
return filtered.slice(0, 6);
|
|
|
}`;
|
|
|
|
|
|
/**
|
|
|
* 截图同款地址 chip:上行门牌街名、下行 City, ST;拒绝 Search 框内多逗号打字态。
|
|
|
*/
|
|
|
export const DETECT_MOTHERSHIP_ADDRESS_CHIP = `(function(arg) {
|
|
|
var widgetSelector = arg.widgetSelector;
|
|
|
var streetNum = arg.streetNum || "";
|
|
|
var cityToken = arg.cityToken || "";
|
|
|
var cityWord = cityToken.trim().split(/\\s+/)[0].toLowerCase();
|
|
|
var widget = document.querySelector(widgetSelector);
|
|
|
if (!widget) return { committed: false, reason: "no-widget" };
|
|
|
|
|
|
for (var i = 0; i < widget.querySelectorAll("input").length; i++) {
|
|
|
var inp = widget.querySelectorAll("input")[i];
|
|
|
if (!inp || inp.offsetParent === null) continue;
|
|
|
var ph = (inp.placeholder || "").toLowerCase();
|
|
|
if (!/search|address|搜索/.test(ph)) continue;
|
|
|
var val = (inp.value || "").replace(/\\s+/g, " ").trim();
|
|
|
if (val.length < 10 || !streetNum || val.indexOf(streetNum) < 0) continue;
|
|
|
var commaParts = val.split(",").map(function (p) { return p.trim(); }).filter(Boolean);
|
|
|
if (commaParts.length >= 2) {
|
|
|
return { committed: false, reason: "active-search-typing", preview: val };
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var raw = (widget.innerText || "").replace(/\\r/g, "");
|
|
|
var lines = raw.split("\\n").map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
|
|
|
|
for (var j = 0; j < lines.length - 1; j++) {
|
|
|
var streetLine = lines[j];
|
|
|
var cityLine = lines[j + 1];
|
|
|
if (!streetNum || streetLine.indexOf(streetNum) < 0) continue;
|
|
|
if (streetLine.split(",").filter(function (p) { return p.trim().length > 0; }).length > 1) continue;
|
|
|
if (!/,\\s*[A-Z]{2}\\b/.test(cityLine)) continue;
|
|
|
if (cityWord && cityLine.toLowerCase().indexOf(cityWord) < 0) continue;
|
|
|
return {
|
|
|
committed: true,
|
|
|
reason: "two-line-chip",
|
|
|
streetLine: streetLine,
|
|
|
cityLine: cityLine,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
return { committed: false, reason: "no-two-line-chip" };
|
|
|
})`;
|
|
|
|
|
|
export const PROBE_CARGO_UI = `(widgetEl, arg) => {
|
|
|
const cargoFieldRe = new RegExp(arg.cargoFieldReSource, "i");
|
|
|
function collectBlockers(el) {
|
|
|
const blockers = [];
|
|
|
if (!el.isConnected) blockers.push("not_connected");
|
|
|
if (el.disabled) blockers.push("disabled");
|
|
|
if (el.readOnly) blockers.push("readonly");
|
|
|
const style = window.getComputedStyle(el);
|
|
|
if (style.visibility === "hidden") blockers.push("visibility_hidden");
|
|
|
if (style.display === "none") blockers.push("display_none");
|
|
|
if (style.pointerEvents === "none") blockers.push("pointer_events_none");
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
if (rect.width <= 0 || rect.height <= 0) blockers.push("zero_bounding_box");
|
|
|
if (typeof el.checkVisibility === "function") {
|
|
|
try {
|
|
|
if (!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
|
|
blockers.push("check_visibility_false");
|
|
|
}
|
|
|
} catch (e) { blockers.push("check_visibility_error"); }
|
|
|
} else if (el.offsetParent === null && style.position !== "fixed") {
|
|
|
blockers.push("offset_parent_null");
|
|
|
}
|
|
|
return blockers;
|
|
|
}
|
|
|
function isInteractive(el) { return collectBlockers(el).length === 0; }
|
|
|
const mountedCargoFields = [];
|
|
|
const interactiveCargoFields = [];
|
|
|
const blockedCargoFields = [];
|
|
|
for (const node of widgetEl.querySelectorAll("input,textarea")) {
|
|
|
const el = node;
|
|
|
const label = (el.placeholder || "") + " " + (el.getAttribute("aria-label") || "");
|
|
|
if (!cargoFieldRe.test(label)) continue;
|
|
|
const snapshot = { placeholder: el.placeholder, ariaLabel: el.getAttribute("aria-label") };
|
|
|
const blockers = collectBlockers(el);
|
|
|
const mounted = el.isConnected && !el.disabled && !el.readOnly;
|
|
|
if (mounted) mountedCargoFields.push(snapshot);
|
|
|
if (isInteractive(el)) interactiveCargoFields.push(snapshot);
|
|
|
else if (mounted || blockers.length > 0) {
|
|
|
blockedCargoFields.push({ placeholder: snapshot.placeholder, ariaLabel: snapshot.ariaLabel, blockers });
|
|
|
}
|
|
|
}
|
|
|
let phase = "no_cargo_inputs";
|
|
|
if (interactiveCargoFields.length > 0) phase = "interactive";
|
|
|
else if (mountedCargoFields.length > 0 || blockedCargoFields.length > 0) phase = "hydrating";
|
|
|
return { phase, mountedCargoFields, interactiveCargoFields, blockedCargoFields };
|
|
|
}`;
|
|
|
|
|
|
export const WAIT_CARGO_INTERACTIVE = `({ selector, cargoReadyReSource }) => {
|
|
|
function isInteractive(el) {
|
|
|
if (!el.isConnected || el.disabled || el.readOnly) return false;
|
|
|
const style = window.getComputedStyle(el);
|
|
|
if (style.visibility === "hidden" || style.display === "none" || style.pointerEvents === "none") return false;
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
|
if (typeof el.checkVisibility === "function") {
|
|
|
try { return el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }); }
|
|
|
catch (e) { return false; }
|
|
|
}
|
|
|
return el.offsetParent !== null || style.position === "fixed";
|
|
|
}
|
|
|
const widget = document.querySelector(selector);
|
|
|
if (!widget) return false;
|
|
|
const readyRe = new RegExp(cargoReadyReSource, "i");
|
|
|
for (const node of widget.querySelectorAll("input,textarea")) {
|
|
|
const el = node;
|
|
|
const label = (el.placeholder || "") + " " + (el.getAttribute("aria-label") || "");
|
|
|
if (!readyRe.test(label)) continue;
|
|
|
if (isInteractive(el)) return true;
|
|
|
}
|
|
|
return false;
|
|
|
}`;
|
|
|
|
|
|
export const FILL_CARGO_FIELD = `(widgetEl, arg) => {
|
|
|
function isInteractive(el) {
|
|
|
if (!el.isConnected || el.disabled || el.readOnly) return false;
|
|
|
const style = window.getComputedStyle(el);
|
|
|
if (style.visibility === "hidden" || style.display === "none" || style.pointerEvents === "none") return false;
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
|
if (typeof el.checkVisibility === "function") {
|
|
|
try { return el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }); }
|
|
|
catch (e) { return false; }
|
|
|
}
|
|
|
return el.offsetParent !== null || style.position === "fixed";
|
|
|
}
|
|
|
const re = new RegExp(arg.fieldRe, "i");
|
|
|
for (const node of widgetEl.querySelectorAll("input,textarea")) {
|
|
|
const el = node;
|
|
|
const label = (el.placeholder || "") + " " + (el.getAttribute("aria-label") || "");
|
|
|
if (!re.test(label) || !isInteractive(el)) continue;
|
|
|
el.focus();
|
|
|
el.value = arg.val;
|
|
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}`;
|