您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
dummy data and fill the trulioo form
// ==UserScript== // @name AutoComplete // @namespace https://trulioo.com/ // @version 1.0.0 // @description dummy data and fill the trulioo form // @author You // @match *://*/eidv/personMatch* // @match *://*/verification* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @connect auto-completed.sitienbmt.workers.dev // ==/UserScript== (function () { 'use strict'; // Config const BACKEND_ENDPOINT = 'https://auto-completed.sitienbmt.workers.dev'; const CLICK_DELAY_MS = 600; // State let countrySelection = null; let isVerification = false; let isKyb = false; let isSearchFieldExisting = false; // Helpers (ported) const dobMonthOpts = [ { text: 'January', value: 1 }, { text: 'February', value: 2 }, { text: 'March', value: 3 }, { text: 'April', value: 4 }, { text: 'May', value: 5 }, { text: 'June', value: 6 }, { text: 'July', value: 7 }, { text: 'August', value: 8 }, { text: 'September', value: 9 }, { text: 'October', value: 10 }, { text: 'November', value: 11 }, { text: 'December', value: 12 }, ]; const genderMap = new Map([ ['M', 'MALE'], ['MALE', 'MALE'], ['F', 'FEMALE'], ['FEMALE', 'FEMALE'], ]); function normalizeInput(input) { return String(input).toLowerCase().replace(/[()]/g, '').trim(); } function normalizeMonthOfBirth(input) { input = String(input).trim(); const numericInput = parseInt(input); if (!isNaN(numericInput) && numericInput >= 1 && numericInput <= 12) { const month = dobMonthOpts.find((m) => m.value === numericInput); return `${month.text} (${month.value})`; } const normalizedInput = normalizeInput(input); const monthByText = dobMonthOpts.find( (m) => normalizeInput(m.text) === normalizedInput ); if (monthByText) return `${monthByText.text} (${monthByText.value})`; const complexMatch = normalizedInput.match(/([a-z]+)\s*(?:\()?(\d+)(?:\))?/); if (complexMatch) { const [, monthText, monthValue] = complexMatch; const month = dobMonthOpts.find( (m) => normalizeInput(m.text) === monthText.trim() || m.value === parseInt(monthValue) ); if (month) return `${month.text} (${month.value})`; } return 'Invalid month input'; } function compareGender(a, b) { if (!a || !b) return false; const norm = (x) => x.trim().toUpperCase(); const extractParen = (s) => (s.match(/\(([^)]+)\)/) || [])[1]; const stripParen = (s) => s.replace(/\s*\([^)]*\)\s*/g, '').trim(); const n1 = norm(a), n2 = norm(b); const c1 = stripParen(n1), c2 = stripParen(n2); const p1 = extractParen(n1), p2 = extractParen(n2); const s1 = genderMap.get(c1) || genderMap.get(p1) || c1; const s2 = genderMap.get(c2) || genderMap.get(p2) || c2; return s1 === s2; } function compareStrings(a, b) { if (!a || !b) return false; a = a.trim().toUpperCase(); b = b.trim().toUpperCase(); if (a === b) return true; const abbr = (s) => (s.match(/\(([^)]+)\)/) || [])[1]; const strip = (s) => s.replace(/\s*\([^)]*\)\s*/g, '').trim(); const aAbbr = abbr(a), bAbbr = abbr(b); const aClean = strip(a), bClean = strip(b); return ( (aAbbr && bClean === aAbbr) || (bAbbr && aClean === bAbbr) || aClean === bClean || (aAbbr && bAbbr && aAbbr === bAbbr) ); } // Environment detection function detectContext() { isVerification = window.location.href.endsWith('verification'); const kybRootId = 'KYBMFComponent'; isKyb = isVerification && !!document.getElementById(kybRootId); if (isVerification) { // Old UI country text const el = isKyb ? document.getElementById(kybRootId) : document.querySelector('td.country-name'); if (el) { const text = isKyb ? document .querySelector('[data-testid=country-selection-inputbox] #search') ?.value : el.textContent; countrySelection = text ? text.toUpperCase() : null; } } else { // New UI country is in search input as "Country (code)" const searchInput = document.querySelector('input[name="search"]'); if (searchInput) { const m = searchInput.value.match(/^(.+?) \(/); if (m) countrySelection = m[1].toUpperCase(); } } } // Field discovery (ported) function getOldUIFields() { const selectors = [ '.mat-input', '[id^=number-range-field-]', '[id^=option-field-]', '[id^=number-range-picker]', 'input.form-control', ]; const fields = [ ...new Set( selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector)).map((item) => (item.getAttribute('id') || '').split('-').pop() ) ) ), ].filter(Boolean); const refIndex = fields.indexOf('Customer Reference ID'); if (refIndex !== -1) fields.splice(refIndex, 1); return fields; } function getNewUIFields() { const fields = [ ...new Set( Array.from(document.querySelectorAll('.form-control')) .map((item) => item.getAttribute('name')) .slice(1) ), ].filter(Boolean); const searchIndex = fields.indexOf('search'); if (searchIndex !== -1) { isSearchFieldExisting = true; fields.splice(searchIndex, 1); const searchFields = document.querySelectorAll('[data-testid$="-search-field"]'); searchFields.forEach((sf) => { const field = sf.getAttribute('data-testid').split('-').shift().trim(); fields.push(field); }); } return fields; } function getFormFields() { if (isVerification) return getOldUIFields(); return getNewUIFields(); } // Parsing and filling (ported) function parseKeyValueLines(text) { const parsed = {}; const invalid = new Set([ 'null', 'none', 'na', 'n/a', '', 'undefined', 'unspecified', 'unknown', 'not applicable', 'not available', 'not provided', ]); text.split('\n').forEach((line) => { const [k, v] = line.split('=').map((p) => p?.trim()); if (k && v && !invalid.has(v.toLowerCase())) parsed[k] = v; }); return parsed; } function handleSelectField(selectEl, responseMap) { const options = selectEl.querySelectorAll('option'); let optionIndex = -1; const field = (selectEl.getAttribute('id') || '').split('-').pop(); const value = responseMap[field]; switch (field) { case 'MonthOfBirth': { const month = normalizeMonthOfBirth(value); optionIndex = Array.from(options).findIndex( (o) => normalizeMonthOfBirth(o.getAttribute('value')) === month ); break; } case 'Gender': optionIndex = Array.from(options).findIndex((o) => compareGender(o.getAttribute('value'), value) ); break; default: optionIndex = Array.from(options).findIndex((o) => compareStrings(o.getAttribute('value'), value) ); break; } if (optionIndex >= 0) { options[optionIndex].selected = true; } else { const randomIndex = Math.floor(Math.random() * options.length); options[randomIndex].selected = true; } } function rejectSearchFields() { if (!isSearchFieldExisting) return; const icons = document.querySelectorAll('[data-icon=xmark]'); if (icons.length > 1) { for (let i = 1; i < icons.length; i++) { icons[i].dispatchEvent(new MouseEvent('click', { bubbles: true, cancellable: true })); } } } function handleSearchField(responseMap) { if (!isSearchFieldExisting) return; document.querySelectorAll('input[name=search]').forEach((searchInput) => { searchInput.click(); document.querySelectorAll('[id$=-dropdown-menu]').forEach((dropDown) => { const options = dropDown.querySelectorAll('[data-testid*=-dropdown-row-]'); const field = dropDown.getAttribute('id').split('-').shift().trim(); const value = responseMap[field]; let option = null; switch (field) { case 'countrySelection': break; case 'MonthOfBirth': { const month = normalizeMonthOfBirth(value); option = Array.from(options).find( (opt) => normalizeMonthOfBirth(opt.textContent) === month ); break; } case 'Gender': option = Array.from(options).find((opt) => compareGender(opt.textContent, value) ); break; default: option = Array.from(options).find((opt) => compareStrings(opt.textContent, value) ); break; } if (option) { option.dispatchEvent( new MouseEvent('click', { bubbles: true, cancellable: true }) ); } }); }); } function resetFormFields() { const fields = getFormFields(); fields.forEach((field) => { const control = document.querySelector(`[name="${field}"], [id$="${field}"]`); if (!control) return; if (control.tagName.toLowerCase() === 'select') { control.selectedIndex = 0; } else { control.value = ''; } control.dispatchEvent(new Event('change', { bubbles: true })); }); rejectSearchFields(); } function clickTestTransactionCheckboxes() { if (isVerification) { // Old UI const consentText = 'I agree T&C*'; document.querySelectorAll('div:has(+ label input[type="checkbox"])').forEach((div) => { if (!div.textContent.includes(consentText)) return; const checkbox = div.nextElementSibling?.querySelector('input[type="checkbox"]'); if (checkbox?.checked === false) checkbox.click(); }); const TEST_TRANSACTION_TEXT = 'Run A Test Transaction'; document.querySelectorAll('input[type="checkbox"] ~ span').forEach((span) => { if (!span.textContent.includes(TEST_TRANSACTION_TEXT)) return; const checkbox = span.previousElementSibling?.previousElementSibling; if (checkbox?.type === 'checkbox' && !checkbox.checked) checkbox.click(); }); } else { // New UI const TEST_TRANSACTION_TEXT = 'Run a Test Transaction'; setTimeout(() => { document.querySelectorAll('input[type="checkbox"] + p').forEach((p) => { if (!p.textContent.includes(TEST_TRANSACTION_TEXT)) return; const checkbox = p.previousElementSibling; if (checkbox?.type === 'checkbox' && !checkbox.checked) checkbox.click(); }); }, CLICK_DELAY_MS); } } // Enhanced form control handling function setNativeValue(el, value) { const prototype = Object.getPrototypeOf(el); const desc = Object.getOwnPropertyDescriptor(prototype, 'value'); if (desc && desc.set) { desc.set.call(el, value); } else { el.value = value; } } function commitInput(el) { el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } function setControlValue(control, value, responseMap) { const tag = control.tagName.toLowerCase(); if (tag === 'select') { handleSelectField(control, responseMap); commitInput(control); return; } if (tag === 'input') { const type = (control.getAttribute('type') || '').toLowerCase(); if (type === 'checkbox') { const desired = value === true || String(value).toLowerCase() === 'true'; if (control.checked !== desired) { control.click(); // click toggles and triggers events in most frameworks } return; } if (type === 'radio') { const name = control.getAttribute('name'); const radios = document.querySelectorAll(`input[type="radio"][name="${name}"]`); const match = Array.from(radios).find(r => compareStrings(r.value, value) || compareStrings(r.id, value) || compareStrings(r.getAttribute('data-value') || '', value)); if (match && !match.checked) { match.click(); } return; } } // Default: text-like controls control.focus(); setNativeValue(control, value); commitInput(control); control.blur(); } function fillFormFields(responseMap) { resetFormFields(); handleSearchField(responseMap); if (isVerification) { if (responseMap.MonthOfBirth) { const idx = dobMonthOpts.findIndex( (m) => normalizeMonthOfBirth(m.text) === normalizeMonthOfBirth(responseMap.MonthOfBirth) ); if (idx !== -1) { responseMap.MonthOfBirth = dobMonthOpts[idx].value; } } } else { // new UI already handled by clickTestTransactionCheckboxes() } for (const [key, value] of Object.entries(responseMap)) { const control = document.querySelector(`[name="${key}"], [id$="${key}"]`); if (!control) continue; setControlValue(control, value, responseMap); } } // Network function postJson(url, body) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(body), onload: (res) => resolve(res), onerror: (err) => reject(err), ontimeout: () => reject(new Error('Timeout')), }); }); } async function fetchExtractedDataFromClipboard(fields) { const clipboardText = await navigator.clipboard.readText(); if (!clipboardText || !clipboardText.trim()) { throw new Error('Clipboard is empty'); } const payload = { fields: fields.join(','), unstructuredData: clipboardText, }; const res = await postJson(`${BACKEND_ENDPOINT}/api/extracting-data`, payload); const json = JSON.parse(res.responseText); if (!json.success) throw new Error(json.message || 'Error extracting data'); return json.result; // expected key=value lines } async function fetchDummyData(fields, countrySelection) { // Optional: country-specific rules from TM storage (if you store them) const rules = GM_getValue( `autocompleted-countrySelectionRules_${countrySelection}`, '' ); const payload = { country: countrySelection, fields: fields.join(','), rule: rules || '', }; const res = await postJson(`${BACKEND_ENDPOINT}/api/dummy-data`, payload); const json = JSON.parse(res.responseText); if (!json.success) throw new Error(json.message || 'Error fetching data'); return json.result; // server returns key=value lines } // Wait for fields to be available async function waitForFields(timeoutMs = 5000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { const fields = getFormFields(); if (fields && fields.length > 0) return fields; await new Promise(r => setTimeout(r, 150)); } return getFormFields(); } // UI trigger function addFloatingButton() { const btn = document.createElement('button'); btn.textContent = 'Auto Fill'; btn.style.cssText = ` position: fixed; z-index: 999999; bottom: 20px; right: 20px; background: #1f6feb; color: #fff; border: none; border-radius: 6px; padding: 10px 14px; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.15); font-size: 14px; `; btn.addEventListener('click', async () => { let success = false; // Disable during processing btn.disabled = true; btn.style.opacity = '0.6'; btn.style.cursor = 'not-allowed'; btn.textContent = 'Auto Fill…'; try { detectContext(); const fields = await waitForFields(); if (!fields || fields.length === 0) { throw new Error('No fields detected.'); } if (!countrySelection) { throw new Error('Country selection not found.'); } clickTestTransactionCheckboxes(); const textContent = await fetchDummyData(fields, countrySelection); const parsed = parseKeyValueLines(textContent); fillFormFields(parsed); // Retry search fields after a delay to let dropdowns mount await new Promise(r => setTimeout(r, 150)); handleSearchField(parsed); success = true; } catch (e) { console.error(e); alert('Auto Fill failed: ' + (e?.message || e)); } finally { if (success) { // Re-enable on success btn.disabled = false; btn.style.opacity = ''; btn.style.cursor = 'pointer'; btn.textContent = 'Auto Fill'; } else { // Keep disabled on failure btn.disabled = true; btn.style.opacity = '0.6'; btn.style.cursor = 'not-allowed'; btn.textContent = 'Auto Fill (disabled)'; } } }); document.body.appendChild(btn); } function addPasteAndFillButton() { const btn = document.createElement('button'); btn.textContent = 'Paste & Fill'; btn.style.cssText = ` position: fixed; z-index: 999999; bottom: 20px; right: 120px; background: #6e40c9; color: #fff; border: none; border-radius: 6px; padding: 10px 14px; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.15); font-size: 14px; `; btn.addEventListener('click', async () => { let success = false; btn.disabled = true; btn.style.opacity = '0.6'; btn.style.cursor = 'not-allowed'; btn.textContent = 'Paste & Fill…'; try { detectContext(); const fields = await waitForFields(); if (!fields || fields.length === 0) { throw new Error('No fields detected.'); } clickTestTransactionCheckboxes(); const textContent = await fetchExtractedDataFromClipboard(fields); const parsed = parseKeyValueLines(textContent); fillFormFields(parsed); await new Promise(r => setTimeout(r, 150)); handleSearchField(parsed); success = true; } catch (e) { console.error(e); alert('Paste & Fill failed: ' + (e?.message || e)); } finally { if (success) { btn.disabled = false; btn.style.opacity = ''; btn.style.cursor = 'pointer'; btn.textContent = 'Paste & Fill'; } else { btn.disabled = true; btn.style.opacity = '0.6'; btn.style.cursor = 'not-allowed'; btn.textContent = 'Paste & Fill (disabled)'; } } }); document.body.appendChild(btn); } // Init window.addEventListener('load', () => { detectContext(); addFloatingButton(); addPasteAndFillButton(); }); })();