Files
Dotbypasser/extension/content/modules/AutoFillHandlerModule.js
2026-01-10 16:53:02 +08:00

481 lines
17 KiB
JavaScript

/**
* ============================================================================
* AutoFill Handler Module (Modularized Version)
* ============================================================================
* 功能:
* 1. 智能識別並填充支付表單 (信用卡、賬單地址)
* 2. 模擬真實用戶輸入事件 (繞過 React/Vue/Angular 的狀態檢查)
* 3. MutationObserver 實時監聽動態加載的表單
* 4. 內置多國賬單地址生成庫 (AVS 繞過)
* 5. 與其他模塊 (Card Generator) 聯動
*
* 使用方式:
* const autofill = new AutoFillHandlerModule(config);
* autofill.init();
* ============================================================================
*/
class AutoFillHandlerModule {
constructor(config = {}) {
this.config = {
enabled: true,
debug: false,
fillCardData: true,
fillBillingData: true,
fillDelay: 500, // 填充延遲 (毫秒),模擬人類思考
typeDelay: 10, // 字符間隔延遲 (可選,用於更高級模擬)
autoSubmit: false, // 是否填充後自動提交
targetCountry: 'US', // 默認賬單國家
// 字段選擇器映射 (支持 CSS 選擇器數組)
selectors: {
cardNumber: [
'input[name*="card"][name*="number"]',
'input[name="cardNumber"]',
'input[id*="cardNumber"]',
'.card-number input',
'input[autocomplete="cc-number"]',
'input[placeholder*="Card number"]'
],
expDate: [
'input[name*="exp"]',
'input[id*="exp"]',
'input[autocomplete="cc-exp"]',
'input[placeholder*="MM / YY"]'
],
cvc: [
'input[name*="cvc"]',
'input[name*="cvv"]',
'input[autocomplete="cc-csc"]',
'input[placeholder*="CVC"]'
],
holderName: [
'input[name*="name"]',
'input[autocomplete="cc-name"]',
'input[id*="holder"]',
'input[placeholder*="Cardholder"]'
],
address: ['input[name*="address"]', 'input[autocomplete="street-address"]', 'input[id*="address"]'],
city: ['input[name*="city"]', 'input[autocomplete="address-level2"]'],
state: ['input[name*="state"]', 'select[name*="state"]', 'input[autocomplete="address-level1"]'],
zip: ['input[name*="zip"]', 'input[name*="postal"]', 'input[autocomplete="postal-code"]'],
country: ['select[name*="country"]', 'select[id*="country"]', 'input[name*="country"]'],
phone: ['input[name*="phone"]', 'input[type="tel"]']
},
// 外部數據源 (如果提供,將覆蓋內部庫)
billingData: null,
...config
};
// 內部狀態
this.observer = null;
this.lastFilledTime = 0;
this.cachedCard = null;
// 內置地址庫 (簡化版,實際使用可擴展)
this.billingDB = {
'US': {
name: 'James Smith',
address: '450 West 33rd Street',
city: 'New York',
state: 'NY',
zip: '10001',
country: 'US',
phone: '2125550199'
},
'GB': {
name: 'Arthur Dent',
address: '42 Islington High St',
city: 'London',
state: '',
zip: 'N1 8EQ',
country: 'GB',
phone: '02079460123'
},
'CN': {
name: 'Zhang Wei',
address: 'No. 1 Fuxingmen Inner Street',
city: 'Beijing',
state: 'Beijing',
zip: '100031',
country: 'CN',
phone: '13910998888'
}
};
// 合併配置中的數據
if (this.config.billingData) {
Object.assign(this.billingDB, this.config.billingData);
}
// 綁定方法
this.handleMutations = this.handleMutations.bind(this);
}
/**
* 初始化模塊
*/
init() {
this.log('Initializing AutoFill Handler Module...');
// 1. 啟動 DOM 監聽
this.startObserver();
// 2. 監聽卡號生成事件 (來自 GOG/Stripe 模塊)
this.setupEventListeners();
// 3. 檢查頁面上已有的表單
if (document.readyState === 'complete' || document.readyState === 'interactive') {
this.scanAndFill(document.body);
} else {
document.addEventListener('DOMContentLoaded', () => this.scanAndFill(document.body));
}
// 4. 暴露全局 API
this.exposeGlobalAPI();
this.log('Module initialized. Waiting for forms...');
}
/**
* 銷毀模塊
*/
destroy() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
this.log('Module destroyed');
}
/**
* ========================================================================
* 事件監聽與聯動
* ========================================================================
*/
setupEventListeners() {
// 監聽 GOG/Stripe 模塊的卡號生成事件
const eventNames = ['GOG_CARD_GENERATED', 'STRIPE_CARD_GENERATED', 'CARD_GENERATED'];
eventNames.forEach(evt => {
document.addEventListener(evt, (e) => {
this.log(`Received card data from ${evt}`, e.detail);
if (e.detail && e.detail.card) {
this.setCardData(e.detail.card);
}
});
});
// 監聽 postMessage
window.addEventListener('message', (event) => {
if (event.data?.eventType === 'CARD_GENERATED') {
this.log('Received card data from postMessage', event.data.card);
this.setCardData(event.data.card);
}
});
}
setCardData(card) {
this.cachedCard = card;
// 收到新卡後立即重新掃描頁面
this.scanAndFill(document.body);
}
/**
* ========================================================================
* Mutation Observer (DOM 監聽)
* ========================================================================
*/
startObserver() {
if (this.observer) return;
this.observer = new MutationObserver(this.handleMutations);
this.observer.observe(document.body, {
childList: true,
subtree: true
});
this.log('DOM Observer started');
}
handleMutations(mutations) {
let shouldScan = false;
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) { // 元素節點
// 簡單過濾:只關注包含 input/select/iframe 的節點
if (node.tagName === 'INPUT' ||
node.tagName === 'SELECT' ||
node.tagName === 'IFRAME' ||
node.querySelector('input, select, iframe')) {
shouldScan = true;
break;
}
}
}
}
if (shouldScan) break;
}
if (shouldScan) {
// 防抖動:不要頻繁掃描
if (this._scanTimeout) clearTimeout(this._scanTimeout);
this._scanTimeout = setTimeout(() => {
this.scanAndFill(document.body);
}, 200);
}
}
/**
* ========================================================================
* 核心填充邏輯
* ========================================================================
*/
async scanAndFill(container) {
if (!this.config.enabled) return;
// 獲取卡數據:優先使用緩存的 (剛生成的),其次嘗試從 Storage 讀取
let cardData = this.cachedCard;
if (!cardData) {
cardData = this.loadCardFromStorage();
}
if (!cardData && this.config.fillCardData) {
// 如果沒有卡數據,我們只能填充地址
this.log('No card data available yet. Skipping card fields.');
}
// 獲取賬單數據
const billingProfile = this.billingDB[this.config.targetCountry] || this.billingDB['US'];
this.log('Scanning container for fields...');
// 1. 填充信用卡字段
if (this.config.fillCardData && cardData) {
await this.fillField(container, this.config.selectors.cardNumber, cardData.number);
// 日期處理:有的表單是 MM / YY 分開,有的是合併
// 這裡簡單處理合併的情況,或者可以擴展檢測邏輯
const expVal = `${cardData.month} / ${cardData.year.slice(-2)}`;
await this.fillField(container, this.config.selectors.expDate, expVal);
await this.fillField(container, this.config.selectors.cvc, cardData.cvc);
}
// 2. 填充賬單字段
if (this.config.fillBillingData && billingProfile) {
await this.fillField(container, this.config.selectors.holderName, billingProfile.name);
await this.fillField(container, this.config.selectors.address, billingProfile.address);
await this.fillField(container, this.config.selectors.city, billingProfile.city);
await this.fillField(container, this.config.selectors.state, billingProfile.state);
await this.fillField(container, this.config.selectors.zip, billingProfile.zip);
await this.fillField(container, this.config.selectors.phone, billingProfile.phone);
// 國家字段比較特殊,通常是 Select
const countryEl = this.findElement(container, this.config.selectors.country);
if (countryEl) {
this.log('Found country field, attempting to set:', billingProfile.country);
this.simulateSelect(countryEl, billingProfile.country);
}
}
}
/**
* 查找並填充單個字段
*/
async fillField(container, selectors, value) {
if (!value) return;
const element = this.findElement(container, selectors);
if (element) {
// 檢查是否已經填充過,避免覆蓋用戶手動輸入
if (element.value && element.value === value) return;
if (element.getAttribute('data-autofilled') === 'true') return;
this.log(`Filling field [${element.name || element.id}] with value length: ${value.length}`);
// 延遲模擬
await this.sleep(this.config.fillDelay);
this.simulateInput(element, value);
element.setAttribute('data-autofilled', 'true');
// 高亮顯示 (可選,便於調試)
if (this.config.debug) {
element.style.backgroundColor = '#e8f0fe';
element.style.transition = 'background-color 0.5s';
}
}
}
/**
* 輔助函數:根據選擇器列表查找元素
*/
findElement(container, selectors) {
for (const selector of selectors) {
// 嘗試查找
const el = container.querySelector(selector);
// 確保元素可見且可編輯
if (el && !el.disabled && el.offsetParent !== null) {
return el;
}
}
return null;
}
/**
* ========================================================================
* 輸入模擬 (核心黑魔法)
* ========================================================================
*/
/**
* 模擬輸入事件序列
* 這是繞過 React/Angular 狀態綁定的關鍵
*/
simulateInput(element, value) {
if (!element) return;
// 1. 獲取並保存原始值屬性描述符
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
// 2. 聚焦
element.focus();
element.dispatchEvent(new Event('focus', { bubbles: true }));
// 3. 設置值 (使用原生 Setter 繞過框架代理)
nativeInputValueSetter.call(element, value);
// 4. 觸發一系列事件
const events = [
new KeyboardEvent('keydown', { bubbles: true }),
new KeyboardEvent('keypress', { bubbles: true }),
new InputEvent('input', { bubbles: true, inputType: 'insertText', data: value }),
new KeyboardEvent('keyup', { bubbles: true }),
new Event('change', { bubbles: true })
];
events.forEach(event => element.dispatchEvent(event));
// 5. 失焦
element.blur();
element.dispatchEvent(new Event('blur', { bubbles: true }));
}
/**
* 模擬下拉框選擇
*/
simulateSelect(element, value) {
if (!element) return;
// 嘗試匹配選項 (Value 或 Text)
let found = false;
for (let i = 0; i < element.options.length; i++) {
const option = element.options[i];
if (option.value === value || option.text.includes(value)) {
element.selectedIndex = i;
found = true;
break;
}
}
if (found) {
element.dispatchEvent(new Event('change', { bubbles: true }));
element.dispatchEvent(new Event('input', { bubbles: true }));
}
}
/**
* ========================================================================
* 工具方法
* ========================================================================
*/
loadCardFromStorage() {
try {
// 嘗試讀取 GOG 模塊的存儲
let json = localStorage.getItem('gogBypasserLastCardJSON');
if (json) return JSON.parse(json);
// 嘗試讀取通用存儲
json = localStorage.getItem('StripeBypasserLastCard');
if (json) return JSON.parse(json);
} catch (e) {
// ignore
}
return null;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 全局 API
*/
exposeGlobalAPI() {
window.autofillHandler = {
module: this,
fill: () => this.scanAndFill(document.body),
setCard: (card) => this.setCardData(card),
updateConfig: (cfg) => { Object.assign(this.config, cfg); },
getBillingProfile: (country) => this.billingDB[country]
};
}
log(...args) {
if (this.config.debug) {
console.log('[AutoFill]', ...args);
}
}
}
/**
* ============================================================================
* 自動初始化
* ============================================================================
*/
window.AutoFillHandlerModule = AutoFillHandlerModule;
if (typeof AUTOFILL_AUTO_INIT !== 'undefined' && AUTOFILL_AUTO_INIT) {
const instance = new AutoFillHandlerModule({
debug: true
});
instance.init();
window.__autofill = instance;
}
// ============================================================================
// Extension Compatibility Wrapper
// ============================================================================
window.addEventListener('message', (event) => {
if (event.source !== window) return;
const message = event.data;
if (message && message.type === 'INIT_MODULE' && message.moduleName === 'autoFill') {
if (window.__autoFillInstance) return;
try {
const instance = new AutoFillHandlerModule(message.config);
instance.init();
window.__autoFillInstance = instance;
console.log('[Extension] AutoFill Handler initialized');
} catch (error) {
console.error('[Extension] AutoFill init failed:', error);
}
}
if (message && message.type === 'DESTROY_MODULE' && message.instanceKey === '__autoFillInstance') {
const instance = window.__autoFillInstance;
if (instance && typeof instance.destroy === 'function') {
instance.destroy();
delete window.__autoFillInstance;
}
}
});