Files
CursorBin/popup.js
2026-01-13 14:44:44 +08:00

570 lines
19 KiB
JavaScript
Raw 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.
/**
* 界面元素引用 (UI References)
*/
const tabs = document.querySelectorAll(".tab-btn");
const tabContents = document.querySelectorAll(".tab-content");
const binInput = document.getElementById("binInput");
const addBinBtn = document.getElementById("addBinBtn");
const generateCardsBtn = document.getElementById("generateCardsBtn");
const statusMessage = document.getElementById("statusMessage");
const binHistoryList = document.getElementById("binHistoryList");
// 地址管理相关元素
const nameInput = document.getElementById("nameInput");
const address1Input = document.getElementById("address1Input");
const address2Input = document.getElementById("address2Input");
const cityInput = document.getElementById("cityInput");
const stateInput = document.getElementById("stateInput");
const zipInput = document.getElementById("zipInput");
const addAddressBtn = document.getElementById("addAddressBtn");
const addressesList = document.getElementById("addressesList");
// 姓名管理相关元素
const firstNameInput = document.getElementById("firstNameInput");
const lastNameInput = document.getElementById("lastNameInput");
const addNameBtn = document.getElementById("addNameBtn");
const namesList = document.getElementById("namesList");
// 设置相关元素
const useLuhnValidation = document.getElementById("useLuhnValidation");
const DEFAULT_BIN = "552461xxxxxxxxxx";
/**
* 格式化输入BIN 输入框
* 自动转大写,移除非数字/X字符
*/
binInput.addEventListener("input", (e) => {
let val = e.target.value.toUpperCase();
val = val.replace(/[^0-9X]/g, "");
if (val.length > 19) {
val = val.substring(0, 19);
}
e.target.value = val;
});
/**
* 格式化输入:粘贴处理
* 自动补全 X确保格式符合 16 位卡号长度
*/
binInput.addEventListener("blur", (e) => {
let val = e.target.value.trim().replace(/[^0-9X]/g, "");
// 如果长度不足且非空,自动补 X
if (val.length > 0 && val.length < 16) {
const currentLen = val.replace(/X/g, "").length;
const needed = 16 - currentLen;
val = val + "X".repeat(needed);
}
e.target.value = val;
});
// ==========================================
// 设置菜单逻辑
// ==========================================
const settingsToggleBtn = document.getElementById("settingsToggleBtn");
const miniSettings = document.getElementById("miniSettings");
if (settingsToggleBtn && miniSettings) {
settingsToggleBtn.addEventListener("click", (e) => {
e.stopPropagation();
miniSettings.classList.toggle("show");
settingsToggleBtn.classList.toggle("active");
});
// 点击外部关闭菜单
document.addEventListener("click", (e) => {
if (!miniSettings.contains(e.target) && e.target !== settingsToggleBtn && !settingsToggleBtn.contains(e.target)) {
miniSettings.classList.remove("show");
settingsToggleBtn.classList.remove("active");
}
});
}
// Luhn 校验开关
if (useLuhnValidation) {
useLuhnValidation.addEventListener("change", () => {
chrome.storage.local.set({
useLuhnValidation: useLuhnValidation.checked
});
});
// 初始化状态
chrome.storage.local.get(["useLuhnValidation"], (data) => {
if (data.useLuhnValidation !== undefined) {
useLuhnValidation.checked = data.useLuhnValidation;
}
});
}
// 数据源选择器逻辑
const addressSourceSelect = document.getElementById("addressSourceSelect");
const nameSourceSelect = document.getElementById("nameSourceSelect");
if (addressSourceSelect) {
addressSourceSelect.addEventListener("change", () => {
const val = addressSourceSelect.value;
chrome.storage.local.set({ addressSource: val });
console.log("✅ Address source changed to:", val);
});
chrome.storage.local.get(["addressSource"], (data) => {
if (data.addressSource) {
addressSourceSelect.value = data.addressSource;
} else {
// 默认设置为 static
addressSourceSelect.value = "static";
chrome.storage.local.set({ addressSource: "static" });
}
});
}
if (nameSourceSelect) {
nameSourceSelect.addEventListener("change", () => {
const val = nameSourceSelect.value;
chrome.storage.local.set({ nameSource: val });
console.log("✅ Name source changed to:", val);
});
chrome.storage.local.get(["nameSource"], (data) => {
if (data.nameSource) {
nameSourceSelect.value = data.nameSource;
} else {
nameSourceSelect.value = "static";
chrome.storage.local.set({ nameSource: "static" });
}
});
}
// 初始化加载所有数据
loadData();
// ==========================================
// 标签页切换逻辑 (Tabs)
// ==========================================
tabs.forEach(tab => {
tab.addEventListener("click", () => {
const targetId = tab.dataset.tab;
tabs.forEach(t => t.classList.remove("active"));
tabContents.forEach(c => c.classList.remove("active"));
tab.classList.add("active");
document.getElementById(targetId + "-tab").classList.add("active");
});
});
const subTabs = document.querySelectorAll(".sub-tab-btn");
const subTabContents = document.querySelectorAll(".sub-tab-content");
subTabs.forEach(tab => {
tab.addEventListener("click", () => {
const targetId = tab.dataset.subtab;
subTabs.forEach(t => t.classList.remove("active"));
subTabContents.forEach(c => c.classList.remove("active"));
tab.classList.add("active");
document.getElementById(targetId + "-subtab").classList.add("active");
});
});
// ==========================================
// 核心功能:添加 BIN 和 生成卡片
// ==========================================
addBinBtn.addEventListener("click", () => {
const bin = binInput.value.trim();
if (!bin) return;
chrome.storage.local.get(["binHistory"], (data) => {
let history = data.binHistory || [];
// 去重并添加到头部
history = history.filter(b => b !== bin);
history.unshift(bin);
// 限制历史记录数量
if (history.length > 20) history = history.slice(0, 20);
chrome.storage.local.set({
binHistory: history,
currentBin: bin
}, () => {
loadBinHistory();
showToast("BIN added to history");
});
});
});
generateCardsBtn.addEventListener("click", async () => {
const bin = binInput.value.trim();
if (!bin) {
showStatus("Please enter a BIN number", "error");
return;
}
if (bin.length < 6) {
showStatus("BIN must be at least 6 digits", "error");
return;
}
const useLuhn = useLuhnValidation.checked;
// UI 状态更新:处理中
generateCardsBtn.disabled = true;
generateCardsBtn.innerHTML = "<span class=\"btn-icon\">⏳</span><span>Processing...</span>";
if (useLuhn) {
showStatus("🔐 Generating cards with Luhn validation...", "loading");
} else {
showStatus("⚡ Generating cards...", "loading");
}
// 保存 BIN 历史
chrome.storage.local.get(["binHistory"], (data) => {
let history = data.binHistory || [];
history = history.filter(b => b !== bin);
history.unshift(bin);
if (history.length > 20) history = history.slice(0, 20);
chrome.storage.local.set({
binHistory: history,
currentBin: bin
}, () => {
loadBinHistory();
});
});
// 发送消息给后台生成卡片
chrome.runtime.sendMessage({
action: "generateCards",
bin: bin,
useValidation: useLuhn,
stripeTabId: null
}, (response) => {
if (response && response.success) {
const luhnMsg = useLuhn ? " (Luhn validated)" : "";
showStatus("✅ Generated " + response.cards.length + " cards" + luhnMsg + ". Filling form...", "loading");
// 查找 Stripe 标签页并注入填充指令
chrome.tabs.query({
url: ["https://checkout.stripe.com/*", "https://*.stripe.com/*"]
}, (tabs) => {
if (chrome.runtime.lastError) {
resetGenerateButton();
showStatus("❌ Error: " + chrome.runtime.lastError.message, "error");
return;
}
if (tabs.length > 0) {
// 优先选择当前激活的标签页
const targetTab = tabs.find(t => t.active) || tabs[0];
chrome.tabs.sendMessage(targetTab.id, {
action: "fillForm"
}, (fillResponse) => {
resetGenerateButton();
if (chrome.runtime.lastError) {
showStatus("❌ No Stripe checkout page found. Please open one first.", "error");
} else {
showStatus("Form filled!", "success");
showToast("✅ Form filled successfully!");
}
});
} else {
resetGenerateButton();
showStatus("❌ No Stripe checkout page found. Please open one first.", "error");
}
});
} else {
resetGenerateButton();
showStatus("Failed to generate cards", "error");
showToast("❌ Failed to generate cards. Try again.", "error");
}
});
});
function resetGenerateButton() {
generateCardsBtn.disabled = false;
generateCardsBtn.innerHTML = "<span class=\"btn-icon\">🚀</span><span>Fill Everything</span>";
}
// ==========================================
// 历史记录管理
// ==========================================
function loadBinHistory() {
chrome.storage.local.get(["binHistory", "currentBin"], (data) => {
const history = data.binHistory || [];
const current = data.currentBin || DEFAULT_BIN;
binInput.value = current;
binHistoryList.innerHTML = "";
if (history.length === 0) {
binHistoryList.innerHTML = "<div class=\"empty\">No BINs saved yet</div>";
return;
}
history.forEach(bin => {
const item = document.createElement("div");
item.className = "history-item";
const binText = document.createElement("span");
binText.textContent = bin;
binText.className = "history-bin";
binText.addEventListener("click", () => {
binInput.value = bin;
chrome.storage.local.set({ currentBin: bin });
showToast("BIN selected");
});
const delBtn = document.createElement("button");
delBtn.textContent = "×";
delBtn.className = "delete-btn";
delBtn.addEventListener("click", (e) => {
e.stopPropagation();
deleteBin(bin);
});
item.appendChild(binText);
item.appendChild(delBtn);
binHistoryList.appendChild(item);
});
});
}
function deleteBin(bin) {
chrome.storage.local.get(["binHistory"], (data) => {
let history = data.binHistory || [];
history = history.filter(b => b !== bin);
chrome.storage.local.set({ binHistory: history }, () => {
loadBinHistory();
showToast("BIN deleted");
});
});
}
// ==========================================
// 地址管理
// ==========================================
addAddressBtn.addEventListener("click", () => {
const name = nameInput.value.trim();
const addr1 = address1Input.value.trim();
const addr2 = address2Input.value.trim();
const city = cityInput.value.trim();
const state = stateInput.value.trim();
const zip = zipInput.value.trim();
if (!name || !addr1 || !city || !state || !zip) {
showToast("Please fill all required fields", "error");
return;
}
// 简单的姓名拆分逻辑
const nameParts = name.split(" ");
const first = nameParts[0] || name;
const last = nameParts.slice(1).join(" ") || nameParts[0];
const newAddr = {
id: Date.now(),
name: name,
firstName: first,
lastName: last,
address1: addr1,
address2: addr2,
city: city,
state: state,
stateCode: getStateCode(state), // 转换州简写 (如 California -> CA)
postal: zip,
countryText: "United States",
countryValue: "US"
};
chrome.storage.local.get(["customAddresses"], (data) => {
const list = data.customAddresses || [];
list.push(newAddr);
chrome.storage.local.set({ customAddresses: list }, () => {
clearAddressInputs();
loadAddresses();
showToast("Address added");
});
});
});
function clearAddressInputs() {
nameInput.value = "";
address1Input.value = "";
address2Input.value = "";
cityInput.value = "";
stateInput.value = "";
zipInput.value = "";
}
function loadAddresses() {
chrome.storage.local.get(["customAddresses"], (data) => {
const list = data.customAddresses || [];
addressesList.innerHTML = "";
if (list.length === 0) {
addressesList.innerHTML = "<div class=\"empty\">No addresses saved yet</div>";
return;
}
list.forEach(addr => {
const item = document.createElement("div");
item.className = "list-item";
const info = document.createElement("div");
info.className = "item-info";
info.innerHTML = `
<strong>${addr.name}</strong><br>
<small>${addr.address1}${addr.address2 ? ", " + addr.address2 : ""}<br>
${addr.city}, ${addr.state} ${addr.postal}</small>
`;
const delBtn = document.createElement("button");
delBtn.textContent = "×";
delBtn.className = "delete-btn";
delBtn.addEventListener("click", () => deleteAddress(addr.id));
item.appendChild(info);
item.appendChild(delBtn);
addressesList.appendChild(item);
});
});
}
function deleteAddress(id) {
chrome.storage.local.get(["customAddresses"], (data) => {
let list = data.customAddresses || [];
list = list.filter(item => item.id !== id);
chrome.storage.local.set({ customAddresses: list }, () => {
loadAddresses();
showToast("Address deleted");
});
});
}
// ==========================================
// 姓名管理
// ==========================================
addNameBtn.addEventListener("click", () => {
const first = firstNameInput.value.trim();
const last = lastNameInput.value.trim();
if (!first || !last) {
showToast("Please enter both first and last name", "error");
return;
}
const newName = {
id: Date.now(),
firstName: first,
lastName: last,
fullName: first + " " + last
};
chrome.storage.local.get(["customNames"], (data) => {
const list = data.customNames || [];
list.push(newName);
chrome.storage.local.set({ customNames: list }, () => {
firstNameInput.value = "";
lastNameInput.value = "";
loadNames();
showToast("Name added");
});
});
});
function loadNames() {
chrome.storage.local.get(["customNames"], (data) => {
const list = data.customNames || [];
namesList.innerHTML = "";
if (list.length === 0) {
namesList.innerHTML = "<div class=\"empty\">No names saved yet</div>";
return;
}
list.forEach(item => {
const div = document.createElement("div");
div.className = "list-item";
const info = document.createElement("div");
info.className = "item-info";
info.innerHTML = `<strong>${item.fullName}</strong>`;
const delBtn = document.createElement("button");
delBtn.textContent = "×";
delBtn.className = "delete-btn";
delBtn.addEventListener("click", () => deleteName(item.id));
div.appendChild(info);
div.appendChild(delBtn);
namesList.appendChild(div);
});
});
}
function deleteName(id) {
chrome.storage.local.get(["customNames"], (data) => {
let list = data.customNames || [];
list = list.filter(item => item.id !== id);
chrome.storage.local.set({ customNames: list }, () => {
loadNames();
showToast("Name deleted");
});
});
}
// ==========================================
// 辅助工具函数
// ==========================================
/**
* 将州全称转换为两字母缩写 (用于表单匹配)
*/
function getStateCode(stateName) {
const map = {
Alabama: "AL", Alaska: "AK", Arizona: "AZ", Arkansas: "AR", California: "CA",
Colorado: "CO", Connecticut: "CT", Delaware: "DE", Florida: "FL", Georgia: "GA",
Hawaii: "HI", Idaho: "ID", Illinois: "IL", Indiana: "IN", Iowa: "IA",
Kansas: "KS", Kentucky: "KY", Louisiana: "LA", Maine: "ME", Maryland: "MD",
Massachusetts: "MA", Michigan: "MI", Minnesota: "MN", Mississippi: "MS", Missouri: "MO",
Montana: "MT", Nebraska: "NE", Nevada: "NV", "New Hampshire": "NH", "New Jersey": "NJ",
"New Mexico": "NM", "New York": "NY", "North Carolina": "NC", "North Dakota": "ND", Ohio: "OH",
Oklahoma: "OK", Oregon: "OR", Pennsylvania: "PA", "Rhode Island": "RI", "South Carolina": "SC",
"South Dakota": "SD", Tennessee: "TN", Texas: "TX", Utah: "UT", Vermont: "VT",
Virginia: "VA", Washington: "WA", "West Virginia": "WV", Wisconsin: "WI", Wyoming: "WY"
};
return map[stateName] || stateName.substring(0, 2).toUpperCase();
}
function showStatus(msg, type = "") {
statusMessage.textContent = msg;
statusMessage.className = "status-message " + type;
statusMessage.style.display = "block";
if (type === "success" || type === "error") {
setTimeout(() => {
statusMessage.style.display = "none";
}, 5000);
}
}
function showToast(msg, type = "success") {
const toast = document.createElement("div");
toast.className = "toast " + type;
toast.textContent = msg;
document.body.appendChild(toast);
// 动画显示
setTimeout(() => {
toast.classList.add("show");
}, 10);
// 自动消失
setTimeout(() => {
toast.classList.remove("show");
setTimeout(() => toast.remove(), 300);
}, 2000);
}
function loadData() {
loadBinHistory();
loadAddresses();
loadNames();
}