Files
CursorBin/content.js
2026-01-13 11:28:37 +08:00

881 lines
31 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 全局状态标记
*/
let isProcessing = false;
let fillButton = null;
let clearButton = null;
/**
* 默认地址数据源
*/
const DEFAULT_ADDRESSES = [{
name: "John Smith",
firstName: "John",
lastName: "Smith",
address1: "69 Adams Street",
address2: "",
city: "Brooklyn",
state: "New York",
stateCode: "NY",
postal: "11201",
countryText: "United States",
countryValue: "US"
}, {
name: "Michael Johnson",
firstName: "Michael",
lastName: "Johnson",
address1: "3511 Carlisle Avenue",
address2: "",
city: "Covington",
state: "Kentucky",
stateCode: "KY",
postal: "41015",
countryText: "United States",
countryValue: "US"
}];
/**
* 字段匹配同义词词典
* 用于启发式识别表单字段类型
*/
const FIELD_SYNONYMS = {
fullName: ["full name", "name", "cardholder name", "card name", "cc-name"],
firstName: ["first name", "given-name"],
lastName: ["last name", "family-name", "surname"],
address1: ["address", "address line 1", "street", "addressline1", "address-line1", "address-line-1"],
address2: ["address line 2", "apt", "apartment", "addressline2", "address-line2", "suite"],
city: ["city", "locality", "address-level2"],
state: ["state", "region", "province", "administrative area", "address-level1", "address level 1"],
postal: ["postal", "zip", "postcode", "postal-code"],
country: ["country", "country or region"]
};
const CARD_FIELD_WORDS = ["card", "cvc", "cvv", "expiry", "expiration", "valid thru", "month", "year"];
// ==========================================
// 核心工具函数
// ==========================================
/**
* 获取随机地址
* 优先从 storage 读取配置,支持 static/manual/auto 模式
*/
async function getRandomAddress() {
return new Promise(resolve => {
chrome.storage.local.get(["customAddresses", "addressSource"], data => {
const customList = data.customAddresses || [];
const sourceMode = data.addressSource || "static";
let addressPool = [];
switch (sourceMode) {
case "static":
addressPool = DEFAULT_ADDRESSES;
break;
case "manual":
addressPool = customList.length > 0 ? customList : DEFAULT_ADDRESSES;
break;
case "auto":
addressPool = DEFAULT_ADDRESSES;
break;
default:
addressPool = DEFAULT_ADDRESSES;
}
if (addressPool.length === 0) {
resolve(DEFAULT_ADDRESSES[0]);
} else {
const selected = addressPool[Math.floor(Math.random() * addressPool.length)];
console.log(`[cardbingenerator] Using ${sourceMode} address:`, selected.name);
resolve(selected);
}
});
});
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function randomDelay(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* 模拟人类输入文本
* @param {HTMLElement} element - 目标输入框
* @param {string} text - 要输入的文本
* @param {boolean} clearFirst - 是否先清空输入框 (默认 false)
*/
async function typeText(element, text, clearFirst = false) {
if (!element || !text) return;
element.focus();
element.scrollIntoView({ behavior: "smooth", block: "center" });
await sleep(randomDelay(150, 300));
if (clearFirst && text.length < 50) {
// 模拟逐字删除或清空
element.value = "";
for (let i = 0; i < text.length; i++) {
element.value += text[i];
element.dispatchEvent(new Event("input", { bubbles: true }));
await sleep(randomDelay(30, 80)); // 模拟击键间隔
}
element.dispatchEvent(new Event("change", { bubbles: true }));
} else {
// 直接赋值模式
element.value = text;
element.dispatchEvent(new Event("input", { bubbles: true }));
element.dispatchEvent(new Event("change", { bubbles: true }));
}
await sleep(randomDelay(100, 200));
element.blur();
await sleep(randomDelay(200, 400));
}
/**
* 收集页面所有根节点(包括 Shadow DOM
* 用于穿透 Shadow DOM 查找元素
*/
function collectRoots() {
const roots = [document];
const queue = [document.documentElement];
while (queue.length) {
const node = queue.pop();
if (!node) continue;
if (node.shadowRoot) {
roots.push(node.shadowRoot);
}
const children = node.children || [];
for (let i = 0; i < children.length; i++) {
queue.push(children[i]);
}
}
return roots;
}
/**
* 检测元素是否可见且可交互
*/
function isVisible(el) {
if (!el) return false;
const rect = el.getBoundingClientRect();
const style = window.getComputedStyle(el);
if (style.visibility === "hidden" || style.display === "none") return false;
if (el.disabled) return false;
if (rect.width <= 0 || rect.height <= 0) return false;
if (el.type === "hidden") return false;
return true;
}
/**
* 收集页面所有可见的表单元素 (input, select, textarea)
*/
function collectFormElements() {
const elements = [];
for (const root of collectRoots()) {
const nodes = root.querySelectorAll("input, select, textarea");
nodes.forEach(node => {
if (isVisible(node)) {
elements.push(node);
}
});
}
return elements;
}
/**
* 使用原生 Setter 设置值并触发事件
* 绕过 React/Vue 等框架的状态绑定限制
*/
async function setNativeValueAndDispatch(element, value, simulateTyping = false) {
if (!element) return;
try {
element.focus();
element.scrollIntoView({ behavior: "smooth", block: "center" });
await sleep(randomDelay(150, 300));
const tagName = element.tagName;
if (tagName === "INPUT" || tagName === "TEXTAREA") {
const proto = tagName === "INPUT" ? window.HTMLInputElement.prototype : window.HTMLTextAreaElement.prototype;
const nativeSetter = Object.getOwnPropertyDescriptor(proto, "value").set;
if (simulateTyping && value && value.length < 30) {
// 模拟打字效果
element.value = "";
for (let i = 0; i < value.length; i++) {
nativeSetter.call(element, element.value + value[i]);
element.dispatchEvent(new Event("input", { bubbles: true }));
await sleep(randomDelay(50, 120));
}
} else {
// 快速填充
nativeSetter.call(element, value);
element.dispatchEvent(new Event("input", { bubbles: true }));
}
element.dispatchEvent(new Event("change", { bubbles: true }));
await sleep(randomDelay(150, 250));
element.blur();
} else if (tagName === "SELECT") {
element.value = value;
element.dispatchEvent(new Event("change", { bubbles: true }));
await sleep(randomDelay(200, 350));
element.blur();
}
await sleep(randomDelay(300, 500));
} catch (err) {
// 降级处理:直接赋值
try {
element.focus();
await sleep(randomDelay(150, 300));
element.value = value;
element.dispatchEvent(new Event("input", { bubbles: true }));
element.dispatchEvent(new Event("change", { bubbles: true }));
await sleep(randomDelay(150, 250));
element.blur();
await sleep(randomDelay(300, 500));
} catch (ignored) {}
}
}
/**
* 处理 Select 下拉框的值选择
* 尝试匹配 value, textContent 或模糊匹配
*/
function pickSelectValue(selectEl, valueOptions, textOptions) {
if (!selectEl || selectEl.tagName !== "SELECT") return null;
const options = Array.from(selectEl.options || []);
const normalize = str => (str || "").toLowerCase().replace(/\s+/g, " ").trim();
const includesText = (text, query) => normalize(text).includes(normalize(query));
// 1. 尝试匹配 value
for (const val of valueOptions || []) {
const found = options.find(opt => normalize(opt.value) === normalize(val));
if (found) return found.value;
}
// 2. 尝试匹配 textContent (精确)
for (const txt of textOptions || []) {
const found = options.find(opt => normalize(opt.textContent) === normalize(txt));
if (found) return found.value;
}
// 3. 尝试匹配 textContent (包含)
for (const txt of textOptions || []) {
const found = options.find(opt => includesText(opt.textContent, txt));
if (found) return found.value;
}
return null;
}
// ==========================================
// 字段识别逻辑
// ==========================================
function getElementTextAttributes(el) {
const attrs = [
el.getAttribute("name"),
el.getAttribute("id"),
el.getAttribute("placeholder"),
el.getAttribute("aria-label"),
el.getAttribute("autocomplete"),
el.getAttribute("data-testid"),
el.getAttribute("data-qa")
].filter(Boolean);
return attrs.join(" ").toLowerCase();
}
function matchesAny(text, keywords) {
const lowerText = text.toLowerCase();
return keywords.some(kw => lowerText.includes(kw.toLowerCase()));
}
function isCardField(el) {
const text = getElementTextAttributes(el);
if (!text) return false;
return matchesAny(text, CARD_FIELD_WORDS);
}
/**
* 计算字段与特定类型的匹配分数
*/
function scoreForSynonyms(el, synonyms) {
const text = getElementTextAttributes(el);
let score = 0;
if (!text) return score;
const autocomplete = (el.getAttribute("autocomplete") || "").toLowerCase();
// 权重最高autocomplete 属性匹配
for (const syn of synonyms) {
if (autocomplete.split(/\s+/).includes(syn.toLowerCase())) {
score += 6;
}
}
// 权重中等name 或 id 包含关键词
const nameId = [(el.getAttribute("name") || "").toLowerCase(), (el.getAttribute("id") || "").toLowerCase()].join(" ");
for (const syn of synonyms) {
if (nameId.includes(syn.toLowerCase())) {
score += 4;
}
}
// 权重最低placeholder 或 aria-label 包含关键词
const desc = [(el.getAttribute("placeholder") || "").toLowerCase(), (el.getAttribute("aria-label") || "").toLowerCase()].join(" ");
for (const syn of synonyms) {
if (desc.includes(syn.toLowerCase())) {
score += 2;
}
}
return score;
}
/**
* 寻找最佳匹配字段
*/
function findBestField(elements, synonyms, validator) {
let bestEl = null;
let bestScore = 0;
for (const el of elements) {
if (validator && !validator(el)) continue;
const score = scoreForSynonyms(el, synonyms);
if (score > bestScore) {
bestEl = el;
bestScore = score;
}
}
return bestEl;
}
/**
* 识别所有非卡号类的普通表单字段 (姓名、地址等)
*/
function detectAddressFields() {
const formElements = collectFormElements().filter(el => !isCardField(el));
const fields = {};
fields.firstName = findBestField(formElements, FIELD_SYNONYMS.firstName, el => el.tagName !== "SELECT");
fields.lastName = findBestField(formElements, FIELD_SYNONYMS.lastName, el => el.tagName !== "SELECT");
fields.fullName = findBestField(formElements, FIELD_SYNONYMS.fullName, el => el.tagName !== "SELECT");
fields.address1 = findBestField(formElements, FIELD_SYNONYMS.address1, el => el.tagName !== "SELECT");
fields.address2 = findBestField(formElements, FIELD_SYNONYMS.address2, el => el.tagName !== "SELECT");
fields.city = findBestField(formElements, FIELD_SYNONYMS.city, el => el.tagName !== "SELECT");
fields.state = findBestField(formElements, FIELD_SYNONYMS.state, el => el.tagName !== "SELECT");
fields.postal = findBestField(formElements, FIELD_SYNONYMS.postal, el => el.tagName !== "SELECT");
fields.country = findBestField(formElements, FIELD_SYNONYMS.country, () => true); // Country 可以是 select
// 逻辑修正:避免全名和名/姓重复匹配
if (fields.fullName) {
if (fields.firstName === fields.fullName) fields.firstName = null;
if (fields.lastName === fields.fullName) fields.lastName = null;
}
if (fields.firstName && fields.lastName && fields.firstName === fields.lastName) {
fields.fullName = fields.firstName;
fields.firstName = null;
fields.lastName = null;
}
return fields;
}
/**
* 专门识别信用卡相关字段
* 使用 CSS 选择器优先匹配
*/
function detectCardFields() {
const roots = collectRoots();
let numEl, expEl, cvcEl;
const numSelectors = ["input[autocomplete=\"cc-number\"]", "input[name*=\"cardnumber\" i]", "input[id*=\"cardnumber\" i]", "input[name=\"cardNumber\"]", "input[placeholder*=\"1234\"]", "#cardNumber"];
const expSelectors = ["input[autocomplete=\"cc-exp\"]", "input[name*=\"exp\" i]", "input[id*=\"exp\" i]", "input[placeholder*=\"MM\"]", "input[name=\"cardExpiry\"]", "#cardExpiry"];
const cvcSelectors = ["input[autocomplete=\"cc-csc\"]", "input[name*=\"cvc\" i]", "input[name*=\"cvv\" i]", "input[id*=\"cvc\" i]", "input[placeholder*=\"CVC\"]", "input[name=\"cardCvc\"]", "#cardCvc"];
function findInRoots(selectors) {
for (const root of roots) {
for (const sel of selectors) {
const el = root.querySelector(sel);
if (el && isVisible(el)) return el;
}
}
return null;
}
numEl = findInRoots(numSelectors);
expEl = findInRoots(expSelectors);
cvcEl = findInRoots(cvcSelectors);
if (numEl || expEl || cvcEl) {
return { number: numEl, exp: expEl, cvc: cvcEl };
}
return null;
}
/**
* 自动点击“手动输入地址”按钮(如果有的话)
* 常见于 Google Places Autocomplete 覆盖了原生输入框的情况
*/
function clickManualAddressIfPresent() {
const keywords = ["enter address manually", "manually enter address", "ввести адрес вручную", "введите адрес вручную", "адрес вручную"];
try {
const roots = collectRoots();
const tagSelectors = ["button", "[role=\"button\"]", "a", ".Button", ".Link", "span[role=\"button\"]", "div[role=\"button\"]"];
for (const root of roots) {
for (const selector of tagSelectors) {
const elements = root.querySelectorAll(selector);
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
if (!isVisible(el)) continue;
const text = [
el.textContent || "",
el.getAttribute("aria-label") || "",
el.getAttribute("title") || "",
el.getAttribute("data-testid") || ""
].join(" ").toLowerCase();
if (!text) continue;
if (keywords.some(kw => text.includes(kw.toLowerCase()))) {
const clickable = el.closest("button, [role=\"button\"], a, [role=\"link\"]") || el;
console.log("[cardbingenerator] Clicking manual address button:", el.textContent);
clickable.click();
return true;
}
}
}
}
} catch (err) {}
return false;
}
// ==========================================
// 主业务逻辑:自动填充流程
// ==========================================
async function autofillAll() {
if (isProcessing) return;
isProcessing = true;
try {
showNotification("🔄 Starting auto-fill...", "info");
await sleep(randomDelay(500, 1000));
// 1. 生成新卡片
showNotification("🔄 Generating fresh cards...", "info");
const storage = await chrome.storage.local.get(["currentBin"]);
const currentBin = storage.currentBin || "552461xxxxxxxxxx";
// 发送消息给 background script 生成卡片
const genResult = await new Promise(resolve => {
chrome.runtime.sendMessage({
action: "generateCards",
bin: currentBin,
stripeTabId: null
}, response => resolve(response));
});
if (!genResult || !genResult.success) {
showNotification("❌ Failed to generate cards: " + (genResult?.error || "Unknown error"), "error");
isProcessing = false;
return;
}
await sleep(2000);
// 2. 获取生成的卡片数据
const cardStorage = await chrome.storage.local.get(["generatedCards"]);
if (!cardStorage.generatedCards || cardStorage.generatedCards.length === 0) {
showNotification("❌ No cards were generated", "error");
isProcessing = false;
return;
}
const selectedCard = cardStorage.generatedCards[Math.floor(Math.random() * cardStorage.generatedCards.length)];
const addressData = await getRandomAddress();
showNotification("💳 Filling card details...", "info");
await sleep(randomDelay(400, 700));
// 3. 处理 Stripe 常见的折叠卡片按钮
const accordionBtn = document.querySelector("[data-testid=\"card-accordion-item-button\"]");
if (accordionBtn && isVisible(accordionBtn)) {
console.log("[cardbingenerator] Clicking card button...");
accordionBtn.scrollIntoView({ behavior: "smooth", block: "center" });
await sleep(randomDelay(300, 500));
accordionBtn.click();
await sleep(randomDelay(800, 1200));
}
// 4. 填充卡片信息
const cardFields = detectCardFields();
if (cardFields) {
if (cardFields.number) {
console.log("[cardbingenerator] Filling card number...");
await setNativeValueAndDispatch(cardFields.number, selectedCard.card_number);
}
if (cardFields.exp) {
console.log("[cardbingenerator] Filling expiry date...");
const expStr = selectedCard.expiry_month + " / " + selectedCard.expiry_year.slice(-2);
await setNativeValueAndDispatch(cardFields.exp, expStr);
}
if (cardFields.cvc) {
console.log("[cardbingenerator] Filling CVC...");
await setNativeValueAndDispatch(cardFields.cvc, selectedCard.cvv);
}
}
// 5. 填充地址信息
showNotification("📝 Filling address...", "info");
const addrFields = detectAddressFields();
// 优先处理国家选择,因为这可能会刷新表单格式
if (addrFields.country && addrFields.country.tagName === "SELECT") {
console.log("[cardbingenerator] Filling country...");
const countryVal = pickSelectValue(addrFields.country, [addressData.countryValue], [addressData.countryText]);
if (countryVal) {
await setNativeValueAndDispatch(addrFields.country, countryVal);
}
await sleep(300);
}
// 检查是否有“手动输入地址”按钮并点击
const clickedManual = clickManualAddressIfPresent();
if (clickedManual) {
console.log("[cardbingenerator] Manual address button clicked");
await sleep(randomDelay(800, 1200));
}
// 填充详细地址字段
const fillAddressDetails = async () => {
const currentFields = detectAddressFields(); // 重新检测,因为 DOM 可能已变化
// 名字处理逻辑
if (currentFields.firstName && currentFields.lastName && currentFields.firstName !== currentFields.lastName) {
console.log("[cardbingenerator] Filling first name...");
await setNativeValueAndDispatch(currentFields.firstName, addressData.firstName, true);
console.log("[cardbingenerator] Filling last name...");
await setNativeValueAndDispatch(currentFields.lastName, addressData.lastName, true);
} else {
const nameField = currentFields.fullName || currentFields.firstName || currentFields.lastName;
if (nameField) {
console.log("[cardbingenerator] Filling full name...");
await setNativeValueAndDispatch(nameField, addressData.name, true);
}
}
if (currentFields.address1) {
console.log("[cardbingenerator] Filling address line 1...");
await setNativeValueAndDispatch(currentFields.address1, addressData.address1, false);
}
if (currentFields.address2) {
console.log("[cardbingenerator] Filling address line 2...");
await setNativeValueAndDispatch(currentFields.address2, addressData.address2, false);
}
if (currentFields.city) {
console.log("[cardbingenerator] Filling city...");
await setNativeValueAndDispatch(currentFields.city, addressData.city, true);
}
if (currentFields.postal) {
console.log("[cardbingenerator] Filling postal code...");
await setNativeValueAndDispatch(currentFields.postal, addressData.postal, false);
}
};
await fillAddressDetails();
// 延迟填充 State/Province因为有些表单需要先填 Country/Zip 才会出现 State
const fillState = async () => {
await sleep(randomDelay(400, 600));
const fieldsNow = detectAddressFields();
if (!fieldsNow.state) return;
if (fieldsNow.state.tagName === "SELECT") {
console.log("📍 Filling state (select)...");
const stateVal = pickSelectValue(fieldsNow.state, [addressData.stateCode], [addressData.state]);
if (stateVal) {
await setNativeValueAndDispatch(fieldsNow.state, stateVal);
}
} else {
console.log("📍 Filling state (input)...");
await setNativeValueAndDispatch(fieldsNow.state, addressData.stateCode || addressData.state, true);
}
};
await fillState();
await sleep(randomDelay(600, 1000));
// 清理已使用的卡片数据
chrome.storage.local.remove(["generatedCards"], () => {
console.log("[cardbingenerator] Cleared used cards from storage");
});
console.log("[cardbingenerator] Auto-fill completed!");
showNotification("✅ All fields filled successfully!", "success");
} catch (err) {
chrome.storage.local.remove(["generatedCards"]);
showNotification("❌ Error: " + err.message, "error");
console.error("Autofill error:", err);
}
isProcessing = false;
}
// ==========================================
// UI 辅助功能:通知和清理按钮
// ==========================================
function showNotification(message, type = "info") {
const existing = document.getElementById("auto-card-filler-notification");
if (existing) existing.remove();
const notif = document.createElement("div");
notif.id = "auto-card-filler-notification";
notif.textContent = message;
const colors = {
info: "#3498db",
success: "#2ecc71",
warning: "#f39c12",
error: "#e74c3c"
};
Object.assign(notif.style, {
position: "fixed",
top: "20px",
right: "20px",
background: colors[type] || colors.info,
color: "white",
padding: "15px 20px",
borderRadius: "8px",
boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
zIndex: "9999999",
fontSize: "14px",
fontWeight: "600",
maxWidth: "300px",
animation: "slideIn 0.3s ease-out"
});
const styleEl = document.createElement("style");
styleEl.textContent = `
@keyframes slideIn {
from { transform: translateX(400px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
if (!document.getElementById("autofill-notification-style")) {
styleEl.id = "autofill-notification-style";
document.head.appendChild(styleEl);
}
document.body.appendChild(notif);
setTimeout(() => {
notif.style.transition = "all 0.3s ease-out";
notif.style.transform = "translateX(400px)";
notif.style.opacity = "0";
setTimeout(() => notif.remove(), 300);
}, 5000);
}
function findPaymentMethodHeader() {
const roots = collectRoots();
for (const root of roots) {
const headers = root.querySelectorAll("h1, h2, h3, h4, .Header, [class*=\"header\"], [class*=\"title\"], [class*=\"Title\"]");
for (const h of headers) {
const text = h.textContent.toLowerCase().trim();
if (["payment method", "payment", "метод оплаты", "способ оплаты"].includes(text) || text.includes("payment method")) {
return h;
}
}
}
return null;
}
function createFillButton() {
if (clearButton || document.getElementById("stripe-clear-btn")) return;
const header = findPaymentMethodHeader();
clearButton = document.createElement("button");
clearButton.id = "stripe-clear-btn";
clearButton.innerHTML = "🗑️ Clear All Data";
// 注入按钮样式
clearButton.style.cssText = `
background: #dc3545;
color: white;
border: none;
border-radius: 8px;
padding: 8px 16px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
transition: all 0.2s ease;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin-left: 8px;
vertical-align: middle;
white-space: nowrap;
`;
clearButton.addEventListener("mouseenter", () => {
clearButton.style.transform = "translateY(-1px)";
clearButton.style.boxShadow = "0 4px 12px rgba(220, 53, 69, 0.4)";
});
clearButton.addEventListener("mouseleave", () => {
clearButton.style.transform = "translateY(0)";
clearButton.style.boxShadow = "0 2px 8px rgba(220, 53, 69, 0.3)";
});
clearButton.addEventListener("click", async () => {
if (confirm(`⚠️ Clear all Stripe data? This will:\n\n• Delete all cookies\n• Clear localStorage\n• Clear sessionStorage\n• Clear cache\n• Reload the page\n\nContinue?`)) {
clearButton.disabled = true;
clearButton.innerHTML = "⏳ Clearing...";
await clearAllStripeData();
showNotification("✅ All data cleared! Reloading...", "success");
setTimeout(() => {
location.reload();
}, 1000);
}
});
// 尝试将按钮插入到支付标题旁边,如果找不到标题则悬浮显示
if (header) {
if (header.parentElement) {
const container = document.createElement("div");
container.style.cssText = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;";
const headerClone = header.cloneNode(true);
headerClone.style.margin = "0";
const btnGroup = document.createElement("div");
btnGroup.style.cssText = "display: flex; gap: 8px;";
btnGroup.appendChild(clearButton);
container.appendChild(headerClone);
container.appendChild(btnGroup);
header.parentElement.replaceChild(container, header);
}
} else {
clearButton.style.cssText += `
position: fixed;
top: 20px;
right: 20px;
z-index: 999999;
padding: 12px 20px;
font-size: 14px;
`;
document.body.appendChild(clearButton);
}
}
/**
* 彻底清理浏览器数据 (Stripe 反指纹追踪)
*/
async function clearAllStripeData() {
try {
localStorage.clear();
sessionStorage.clear();
// 清理 IndexedDB
if (window.indexedDB) {
const dbs = await window.indexedDB.databases();
for (const db of dbs) {
window.indexedDB.deleteDatabase(db.name);
}
}
// 暴力清理 Cookies
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=" + location.hostname;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=." + location.hostname;
}
if ("caches" in window) {
const keys = await caches.keys();
for (const key of keys) {
await caches.delete(key);
}
}
chrome.runtime.sendMessage({ action: "clearBrowsingData" });
console.log("[cardbingenerator] All Stripe data cleared");
} catch (err) {
console.error("Error clearing data:", err);
showNotification("⚠️ Partial clear - some data may remain", "warning");
}
}
function shouldShowButton() {
const hasCardFields = detectCardFields();
const hasAddrFields = detectAddressFields();
return hasCardFields || hasAddrFields.fullName || hasAddrFields.address1;
}
function initButton() {
if (shouldShowButton()) {
createFillButton();
}
}
// ==========================================
// 初始化与事件监听
// ==========================================
const observer = new MutationObserver(() => {
if (!fillButton && shouldShowButton()) {
createFillButton();
}
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initButton);
} else {
initButton();
}
// 多次尝试初始化,应对动态加载的 SPA
setTimeout(initButton, 1000);
setTimeout(initButton, 2000);
setTimeout(initButton, 3000);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "fillForm") {
autofillAll();
}
});
window.addEventListener("beforeunload", () => {
chrome.storage.local.remove(["generatedCards"]);
console.log("[cardbingenerator] Cleared cards on page unload");
});
chrome.storage.local.remove(["generatedCards"], () => {
console.log("[cardbingenerator] Cleared old cards on page load");
});