// ==UserScript== // @name AliExpress Card Master // @description Заполняет данные карты для оплаты // @author longnull // @namespace longnull // @version 1.0.10 // @updateURL https://gist.github.com/longnull/2eafe3b843a8152c8864f7b5ba698b19/raw/AliExpressCardMaster.user.js // @downloadURL https://gist.github.com/longnull/2eafe3b843a8152c8864f7b5ba698b19/raw/AliExpressCardMaster.user.js // @match https://shoppingcart.aliexpress.us/orders.htm* // @match https://shoppingcart.aliexpress.us/order/confirm_order.htm* // @match https://www.aliexpress.us/p/trade/confirm.html* // @match https://www.aliexpress.us/p/second-payment/index.html* // @match https://m.aliexpress.us/p/second-payment/index.html* // @match https://m.aliexpress.us/p/trade/confirm.html* // @match https://aliexpress.us/checkout* // @match https://shoppingcart.aliexpress.com/orders.htm* // @match https://shoppingcart.aliexpress.com/order/confirm_order.htm* // @match https://www.aliexpress.com/p/trade/confirm.html* // @match https://www.aliexpress.com/p/second-payment/index.html* // @match https://m.aliexpress.com/p/second-payment/index.html* // @match https://m.aliexpress.com/p/trade/confirm.html* // @match https://aliexpress.ru/checkout* // @match https://www.aliexpress.us/p/trade/confirm.html* // @match https://intl-qk-global-inner.alipay.com/bindcard/selfaddcard.htm* // ==/UserScript== (() => { 'use strict'; // ================================================================================================================================================================ // Изменения // ================================================================================================================================================================ // v1.0.10 // - Обновлён под новый сайт aliexpress.ru (также нужно обновить скрипт для карт - https://gist.github.com/longnull/2eafe3b843a8152c8864f7b5ba698b19/raw/AliExpressCardMasterCards.user.js) // v1.0.9 // - Добавлен новый адрес (также нужно обновить скрипт для карт - https://gist.github.com/longnull/2eafe3b843a8152c8864f7b5ba698b19/raw/AliExpressCardMasterCards.user.js) // - Обновлены селекторы для месяца и года // - Изменено ожидание после ввода номера карты // v1.0.8 // - Актуализированы адреса и селекторы под все страницы (также нужно обновить скрипт для карт - https://gist.github.com/longnull/2eafe3b843a8152c8864f7b5ba698b19/raw/AliExpressCardMasterCards.user.js) // - Для параметра select добавлены значения 'place' и 'pay' // v1.0.7 // - В параметрах карт добавлен параметр select (автоматический выбор карты) // - Исправлено ожидание загрузки информации о карте после ввода номера // v1.0.6 // - Обновлён под новую форму // v1.0.5 // - Теперь карты можно вставить в отдельный скрипт // - Добавлен ещё один селектор для кнопки подтверждения карты // ================================================================================================================================================================ // Карты // ================================================================================================================================================================ // Чтобы добавить карту, нужно скопировать имеющуюся от метки начала до метки конца, вставить рядом с другой картой и изменить поля // Всегда храните свои карты в отдельном файле, не храните их только в скрипте, иначе потеряете их при обновлении скрипта // // Карты можно вставить в отдельный скрипт, чтобы не приходилось их вставлять обратно в основной скрипт после обновлений // Скрипт для вставки карт: https://gist.github.com/longnull/2eafe3b843a8152c8864f7b5ba698b19/raw/AliExpressCardMasterCards.user.js // Иногда может быть необходимо обновить скрипт с картами, если, например, в основном скрипте изменились @match строки вверху (они должны совпадать) let cards = [ // --- Начало карты --- { // Имя карты в списке name: 'Случайная', // true - сохранить карту // false - не сохранять карту save: false, // true - подтвердить после заполнения // false - не подтверждать после заполнения submit: true, // Автоматический выбор карты // true - выбирать эту карту автоматически // false - не выбирать эту карту автоматически // 'place' - выбирать эту карту автоматически только на странице оформления // 'pay' - выбирать эту карту автоматически только на странице оплаты select: true, // Номер карты // %rnd% - случайная карта // %rndvisa% - случайная карта Visa // %rndmaster% - случайная карта MasterCard number: 'nr', // Срок действия: месяц expiryMonth: '12', // Срок действия: год expiryYear: '29', // CVV/CVC securityCode: 'ccv', // Держатель карты: имя holderFirstName: '', // Держатель карты: фамилия holderLastName: '', // Ниже - поля для Alipay // Страна country: 'US', // Область/регион/штат province: 'Colorado', // Город city: '%rw(6-12,1)%', // Адрес address: '%rw(8-20,a)%', // Адрес 2 address2: '%rw(8-20,a)%', // Индекс zip: '%rn(10000-99999)%', }, // --- Конец карты --- ]; // ================================================================================================================================ // Настройки // ================================================================================================================================ const config = { // BIN для генерации Visa binVisa: [ '414734', '415975', '419402', '421075', '422438', '424604', '425838', '426628', '428038', '430064', ], // BIN для генерации MasterCard binMastercard: [ '510594', '511079', '514138', '517552', '519280', '521990', '524366', '527368', '530860', '533248', ], // Количество элементов в выпадающем списке, отображаемое без прокрутки itemsWithoutScroll: 6, // Режим отладки debug: false }; // ================================================================================================================================ // https://github.com/grahamking/darkcoding-credit-card const strRev = (str) => { if (!str) { return ''; } let revstr = ''; for (let i = str.length - 1; i >= 0; i--) { revstr += str.charAt(i); } return revstr; }; const completedNumber = (prefix, length) => { let ccnumber = prefix; while (ccnumber.length < (length - 1)) { ccnumber += Math.floor(Math.random() * 10); } const reversedCCnumberString = strRev(ccnumber); const reversedCCnumber = []; for (let i = 0; i < reversedCCnumberString.length; i++) { reversedCCnumber[i] = parseInt(reversedCCnumberString.charAt(i)); } let sum = 0; let pos = 0; while (pos < length - 1) { let odd = reversedCCnumber[pos] * 2; if (odd > 9) { odd -= 9; } sum += odd; if (pos != (length - 2)) { sum += reversedCCnumber[pos + 1]; } pos += 2; } const checkdigit = ((Math.floor(sum / 10) + 1) * 10 - sum) % 10; ccnumber += checkdigit; return ccnumber; }; const creditCardNumber = (prefixList, length, howMany) => { const result = []; for (let i = 0; i < howMany; i++) { const randomArrayIndex = Math.floor(Math.random() * prefixList.length); const ccnumber = prefixList[randomArrayIndex]; result.push(completedNumber(ccnumber, length)); } return result; }; // ================================================================================================================================ const log = { debug(...args) { if (config && config.debug) { const ar = Array.prototype.slice.call(args, 0); ar.unshift('ACM ::'); console.log(...ar); } } }; const sleep = async (ms) => { return new Promise((resolve) => { setTimeout(resolve, ms); }); }; const waitForRequest = (url, startTimeout = 0, endTimeout = 0, max = 0) => { log.debug('waitForRequest : url =', url, ', startTimeout =', startTimeout, ', endTimeout =', endTimeout, ', max =', max); if (!Array.isArray(url)) { url = [url]; } const openOrig = unsafeWindow.XMLHttpRequest.prototype.open; const promise = new Promise((resolve) => { let startTimer; let endTimer; let requestCount = 0; let responseCount = 0; let urlIndex = 0; const resolveAndRestore = () => { resolve(); unsafeWindow.XMLHttpRequest.prototype.open = openOrig; }; unsafeWindow.XMLHttpRequest.prototype.open = function () { log.debug('waitForRequest : request :', arguments); if (!arguments[1].includes(url[urlIndex])) { return openOrig.apply(this, arguments); } requestCount++; urlIndex++; if (startTimer) { clearTimeout(startTimer); startTimer = null; } if (endTimer) { clearTimeout(endTimer); endTimer = null; } log.debug('waitForRequest : request match :', arguments[1]); this.addEventListener('load', function () { log.debug('waitForRequest : response :', this.responseURL); responseCount++; if (!endTimeout || (max && responseCount >= max)) { return resolveAndRestore(); } if (requestCount == responseCount) { if (urlIndex >= url.length) { resolveAndRestore(); } endTimer = setTimeout(() => { resolveAndRestore(); }, endTimeout); } }); this.addEventListener('error', () => { log.debug('waitForRequest : error'); resolveAndRestore(); }); this.addEventListener('abort', () => { log.debug('waitForRequest : abort'); resolveAndRestore(); }); return openOrig.apply(this, arguments); }; if (startTimeout) { startTimer = setTimeout(() => { resolveAndRestore(); }, startTimeout); } }); promise.catch(() => { log.debug('waitForRequest : catch'); unsafeWindow.XMLHttpRequest.prototype.open = openOrig; }); return promise; }; const waitForElement = (selectors, waitForExistence = true, visible = false, parent = document, interval = 250, seconds = 0) => { log.debug('waitForElement : selectors =', selectors, ', waitForExistence =', waitForExistence, ', visible =', visible, ', parent =', parent, ', interval =', interval, ', seconds =', seconds); const isVisible = (e) => { return !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length); }; return new Promise((resolve) => { if (!Array.isArray(selectors)) { selectors = [selectors]; } seconds = seconds * 1000; const startTime = Date.now(); const check = () => { let found = true; for (const s of selectors) { const el = parent.querySelector(s); if ((waitForExistence && (!el || (visible && !isVisible(el)))) || (!waitForExistence && el)) { found = false; break; } } if (found) { return resolve(true); } if (seconds > 0 && Date.now() - startTime > seconds) { return resolve(false); } setTimeout(check, interval); }; check(); }); }; function randomizeText(text, vars = {}, index = 0, type = 0) { // https://stackoverflow.com/a/52789490 const formatDate = (date, patternStr) => { if (!patternStr) { patternStr = 'dd.MM.yyyy'; } const monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; const dayOfWeekNames = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ]; const day = date.getDate(), month = date.getMonth(), year = date.getFullYear(), hour = date.getHours(), minute = date.getMinutes(), second = date.getSeconds(), miliseconds = date.getMilliseconds(), h = hour % 12, hh = twoDigitPad(h), HH = twoDigitPad(hour), mm = twoDigitPad(minute), ss = twoDigitPad(second), aaa = hour < 12 ? 'AM' : 'PM', EEEE = dayOfWeekNames[date.getDay()], EEE = EEEE.substr(0, 3), dd = twoDigitPad(day), M = month + 1, MM = twoDigitPad(M), MMMM = monthNames[month], MMM = MMMM.substr(0, 3), yyyy = year + '', yy = yyyy.substr(2, 2); patternStr = patternStr .replace('hh', hh).replace('h', h) .replace('HH', HH).replace('H', hour) .replace('mm', mm).replace('m', minute) .replace('ss', ss).replace('s', second) .replace('S', miliseconds) .replace('dd', dd).replace('d', day) .replace('EEEE', EEEE).replace('EEE', EEE) .replace('yyyy', yyyy) .replace('yy', yy) .replace('aaa', aaa); if (patternStr.indexOf('MMM') > -1) { patternStr = patternStr .replace('MMMM', MMMM) .replace('MMM', MMM); } else { patternStr = patternStr .replace('MM', MM) .replace('M', M); } return patternStr; }; const twoDigitPad = (num) => { return num < 10 ? '0' + num : num; }; const randomString = (length, chars = 'zZ0') => { let characters = ''; if (chars.search(/[a-z]/) !== -1) { characters += 'abcdefghijklmnopqrstuvwxyz'; } if (chars.search(/[A-Z]/) !== -1) { characters += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; } if (chars.search(/[а-яё]/) !== -1) { characters += 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'; } if (chars.search(/[А-ЯЁ]/) !== -1) { characters += 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'; } if (chars.search(/[0-9]/) !== -1) { characters += '0123456789'; } const charactersLength = characters.length; let result = ''; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; }; // http://jsfiddle.net/amando96/XjUJM/ const randomWord = (length, capitalize = false) => { const consonants = 'bcdfghjlmnpqrstv'.split(''); const vowels = 'aeiou'.split(''); let word = ''; for (let i = 0; i < length / 2; i++) { const randConsonant = consonants[randomRange(0, consonants.length - 1)]; const randVowel = vowels[randomRange(0, vowels.length - 1)]; word += capitalize && i === 0 ? randConsonant.toUpperCase() : randConsonant; word += i * 2 < length - 1 ? randVowel : ''; } return word; }; const randomRange = (min, max) => { return Math.floor(Math.random() * (max + 1 - min)) + min; }; // https://medium.com/@nitinpatel_20236/how-to-shuffle-correctly-shuffle-an-array-in-javascript-15ea3f84bfb const arrayShuffle = (array) => { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); const temp = array[i]; array[i] = array[j]; array[j] = temp; } return array; }; const funcs = { rs(params) { if (!params || !params.length) { return ''; } let len; let chars = params.length > 1 ? params[1] : 'aA0'; const spl = params[0].split('-'); if (spl.length > 1) { const min = parseInt(spl[0].trim()); const max = parseInt(spl[1].trim()); if (!min || !max) { return ''; } len = randomRange(min, max); } else { len = parseInt(spl[0]); } if (!len) { return ''; } return randomString(len, chars); }, rw(params) { if (!params || !params.length) { return ''; } let len; let cap = (params.length > 1 && (params[1] === '1' || params[1].toLowerCase() === 'true')) ? true : false; const spl = params[0].split('-'); if (spl.length > 1) { const min = parseInt(spl[0].trim()); const max = parseInt(spl[1].trim()); if (!min || !max) { return ''; } len = randomRange(min, max); } else { len = parseInt(spl[0]); } if (!len) { return ''; } return randomWord(len, cap); }, rn(params) { if (!params || !params.length) { return ''; } const spl = params[0].split('-'); if (spl.length < 2) { return ''; } const min = parseInt(spl[0].trim()); const max = parseInt(spl[1].trim()); if (min === NaN || max === NaN) { return ''; } return randomRange(min, max); }, dt(params) { if (!params || !params.length) { return formatDate(new Date()); } return formatDate(new Date(), params[0]); } }; const sub = index > 0; const arr = []; let cur = ''; while (index < text.length) { if (text[index] == '{' || text[index] == '[') { const res = randomizeText(text, vars, index + 1, text[index] == '{' ? 0 : 1); cur += res.text; index = res.index; } else { if (text[index] == '|') { arr.push(cur); cur = ''; } else if (sub && ((type == 0 && text[index] == '}') || (type == 1 && text[index] == ']'))) { arr.push(cur); index++; break; } else { cur += text[index]; } index++; } } if (sub) { let result; let value; const varName = arr[arr.length - 1].match(/\$(.+)/); if (varName) { arr[arr.length - 1] = arr[arr.length - 1].replace(/\$(.+)/, ''); } if (type == 0) { if (arr.length > 1) { value = arr[randomRange(0, arr.length - 1)]; result = {text: value, index: index}; } else if (arr.length == 1) { if (randomRange(1, 100) > 50) { value = arr[0]; result = {text: arr[0], index: index}; } else { value = ''; result = {text: value, index: index}; } } } else if (type == 1) { const sep = arr.pop(); arrayShuffle(arr); value = arr.join(sep); result = {text: value, index: index}; } if (varName) { vars[varName[1]] = value; } return result; } cur = cur.replace(/\%([a-zA-Z_]+?)\((.+?)\)(?:\$(.+?))?\%/g, (m, func, params, varName) => { if (funcs[func]) { let paramsArray = []; if (params) { paramsArray = params.trim().split(',').map((p) => p.trim()); } const res = funcs[func](paramsArray); if (varName) { vars[varName] = res; } return res; } }); const keys = Object.keys(vars); for (const k of keys) { cur = cur.replace(new RegExp(`\\%${k}\\%`, 'g'), vars[k]); } return cur; }; const setInput = (input, value) => { if (typeof input === 'string') { input = document.querySelector(input); } if (!input) { return; } if (Object.keys(input).filter((i) => i.indexOf('__reactEventHandlers') >= 0).length) { Object.getOwnPropertyDescriptor(window[input.constructor.name].prototype, 'value').set.call(input, value); } else { input.value = value; } input.dispatchEvent(new Event('change', {bubbles: true})); input.dispatchEvent(new Event('keyup', {bubbles: true})); input.dispatchEvent(new Event('keydown', {bubbles: true})); input.dispatchEvent(new Event('keypress', {bubbles: true})); input.dispatchEvent(new Event('input', {bubbles: true})); input.dispatchEvent(new Event('blur', {bubbles: true})); }; const init = () => { if (document.querySelector('#card-master')) { return; } const form = document.querySelector(` .payment-container-body.comet-drawer-body > div[class] ~ div:nth-child(2), div[class*="add-card--m-card-form"], div[class*="CheckoutPaymentMethod_BackCardForm__cardForm"], .card-info > div:first-child, .new-card-wrap > div:first-child, div[id*="paymentMethod_"], #bindcard-form `); if (!form) { return; } log.debug('init'); log.debug(form); if (unsafeWindow.acmCards) { cards = unsafeWindow.acmCards; } const payPage = window.location.pathname == '/p/second-payment/index.html' || window.location.pathname == '/order/secondPayment.htm' || window.location.pathname == '/order/secpay.html'; const main = document.createElement('div'); main.id = 'card-master'; const select = document.createElement('div'); select.className = 'select'; select.innerHTML = `
Выберите карту
`; select.addEventListener('click', (event) => { event.preventDefault(); if (!select.classList.contains('opened') && select.classList.contains('disabled')) { return; } select.classList.toggle('opened'); return false; }); const ul = document.createElement('ul') select.appendChild(ul); let autoSelect; for (let i = 0; i < cards.length; i++) { const card = cards[i]; const li = document.createElement('li'); li.textContent = card.name; li.dataset.dataIdx = i; li.addEventListener('click', async (event) => { event.preventDefault(); select.classList.add('disabled'); const card = cards[i]; if (card.number) { log.debug('waiting for queryCardBinInfo and fill card number'); await Promise.all([ waitForRequest(['/queryCardBinInfo.htm', '/h5/mtop.global.payment.ae.async'], 1000, 1000), new Promise((resolve) => { const number = card.number .replace('%rnd%', creditCardNumber(config.binVisa.concat(config.binMastercard), 16, 1)) .replace('%rndvisa%', creditCardNumber(config.binVisa, 16, 1)) .replace('%rndmaster%', creditCardNumber(config.binMastercard, 16, 1)); setInput('#cardNum, #cardNumber, .card-num, input[name="card_number"], #j-card-number', number); if (document.querySelector('.billing-address')) { if (number.startsWith('4')) { document.querySelector('#j-label-visa input').click(); } else { document.querySelector('#j-label-mastercard input').click(); } } resolve(); }) ]); } if (card.holderFirstName && card.holderLastName) { log.debug('filling card holder'); if (document.querySelector('input[name="firstName"]')) { setInput('input[name="firstName"]', randomizeText(card.holderFirstName)); setInput('input[name="lastName"]', randomizeText(card.holderLastName)); } else { setInput( '#cardHolder, #cardholder, .card-holder input, input[name="card_name"]', `${randomizeText(card.holderFirstName)} ${randomizeText(card.holderLastName)}` ); } } if (card.expiryMonth && card.expiryYear) { log.debug(`filling card expiry : ${card.expiryMonth}/${card.expiryYear}`); if (document.querySelector('input[name="expiryMonth"]')) { setInput('input[name="expiryMonth"]', card.expiryMonth); setInput('input[name="expiryYear"]', card.expiryYear); } else { let elMonth = document.querySelector('.payment-container-body .comet-select:first-child, div[class*="expire-input--expires-row"] .comet-select:first-child'); let elYear = document.querySelector('.payment-container-body .comet-select:last-child, div[class*="expire-input--expires-row"] .comet-select:last-child'); if (elMonth && elYear) { elMonth.click(); await sleep(300); elMonth = document.querySelector(`.comet-select-popup-wrap .comet-menu .comet-menu-item[label="${(card.expiryMonth.length == 1 ? '0' : '') + card.expiryMonth}"]`); if (elMonth) { elMonth.click(); } elYear.click(); await sleep(300); elYear = document.querySelector(`.comet-select-popup-wrap .comet-menu .comet-menu-item[label="${card.expiryYear}"]`); if (elYear) { elYear.click(); } } else { setInput(` #expires, #expire, #cardExpire, .card-expires input, div[id*="paymentMethod_"] input[name="card_number"] + div input[inputmode="numeric"] `, `${card.expiryMonth}/${card.expiryYear}` ); } } } let el = document.querySelector('.new-card-wrap .next-btn, div[id*="paymentMethod_"] > div > div > div:nth-child(2) div'); if (el) { log.debug('clicking next button'); el.click(); await sleep(300); } if (typeof card.save !== 'undefined') { if (card.save) { el = document.querySelector(` .payment-container-body .comet-checkbox:not(.comet-checkbox-checked), div[class*="save-card--save-card-row"] .comet-checkbox:not(.comet-checkbox-checked), div[class*="save-card--save-card-row"] .comet-switch:not(.comet-switch-checked), div[class*="CheckoutPaymentMethod_BackCardForm__saveCardCheckbox"]:not([class*="ali-kit_Checkbox__checked"]), .save-card .next-checkbox-wrapper:not(.checked), div[id*="paymentMethod_"] > div > div > div:nth-child(3) > div:last-child > div.hxYBDU `); } else { el = document.querySelector(` .payment-container-body .comet-checkbox.comet-checkbox-checked, div[class*="save-card--save-card-row"] .comet-checkbox.comet-checkbox-checked, div[class*="save-card--save-card-row"] .comet-switch.comet-switch-checked, div[class*="CheckoutPaymentMethod_BackCardForm__saveCardCheckbox"][class*="ali-kit_Checkbox__checked"], .save-card .next-checkbox-wrapper.checked, div[id*="paymentMethod_"] > div > div > div:nth-child(3) > div:last-child > div.jzqzHB `); } if (el) { log.debug('clicking save switch'); el.click(); } else { el = document.querySelector('.card-save-button .switch input'); if (el && el.checked != card.save) { log.debug('clicking save switch'); document.querySelector('.card-save-button .switch').click(); } } if (!card.save) { await sleep(300); el = document.querySelector(` .comet-modal-footer button:first-child, div[class*="save-card--save-card-info-confirm"] button:first-child, .save-card-info-confirm .next-btn-normal `); if (el) { log.debug('clicking skip button'); el.click(); } } } if (card.securityCode) { log.debug('filling card cvc :', card.securityCode); setInput(` #cvv, #cvc, #cardCvv, .card-cvv input, div[id*="paymentMethod_"] > div > div > div > form > div:nth-child(2) input, input[name="cvv2"] `, card.securityCode ); } if (document.querySelector('.billing-address')) { log.debug('filling alipay info'); const addrRadio = document.querySelector('.billing-address .address-radio[value="1"]'); if (addrRadio && (card.address || card.address2 || card.city || card.country || card.province || card.zip)) { addrRadio.click(); } if (card.address) { setInput('#address1', randomizeText(card.address)); } if (card.address2) { setInput('#address2', randomizeText(card.address2)); } if (card.city) { setInput('#city', randomizeText(card.city)); } if (card.country) { setInput('#country', randomizeText(card.country)); } if (card.province) { setInput('#state:not(.fn-hide), .i-select-province:not(.fn-hide) #state-select', randomizeText(card.province)); } if (card.zip) { setInput('#postCode', randomizeText(card.zip)); } } if (card.submit && !payPage) { log.debug('submitting'); setTimeout(() => { const submitEl = document.querySelector(` #payment-botton-section button, button[class*="CheckoutPaymentMethod_ButtonRow__controlAccent"], .dialog-header .dialog-btn, .i-button-submit `); if (submitEl) { submitEl.click(); } }, 500); } log.debug('done'); select.classList.remove('disabled'); return false; }); ul.appendChild(li); if (card.select === true || (card.select === 'place' && !payPage) || (card.select === 'pay' && payPage)) { autoSelect = li; } } main.appendChild(select); const style = document.createElement('style'); style.innerHTML = ` #card-master { font-size: 16px; margin-bottom: 4px; height: 30px; } .addcard-form-wrapper #card-master, .self-bindcard #card-master { margin-top: 4px; } .self-bindcard #card-master { padding-left: 90px; } .pay-content #card-master, .card-info #card-master, .new-card-wrap #card-master { margin-bottom: 8px; } #card-master .select { position: relative; display: flex; flex-direction: column; width: 250px; background-color: white; border: 1px solid #dcdee3; border-radius: 4px; z-index: 1000; } #card-master .select.disabled > div { color: #bbb; } #card-master .select > div { display: flex; align-items: center; padding: 2px 6px; color: #333; background-color: white; border-radius: 4px; user-select: none; } #card-master .select > div::after { content: '▼'; right: 4px; position: absolute; color: #dcdee3; } #card-master .select.opened > div::after { content: '▲'; } #card-master .select > div > svg { margin-right: 4px; fill: #51b1f0; width: 22px; height: 22px; } #card-master .select.disabled > div > svg { fill: #bbb; } #card-master .select > ul { display: none; border-radius: 0 0 4px 4px; } #card-master .select.opened > ul { display: block; background: white; border-top: 2px solid #dcdee3; max-height: ${config.itemsWithoutScroll * 32 - 1}px; overflow-x: hidden; overflow-y: auto; } #card-master .select.opened > ul > li { height: 27px; padding: 2px 6px; color: #333; user-select: none; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } #card-master .select.disabled.opened > ul > li { color: #bbb; } #card-master .select.opened > ul > li:not(:last-child) { border-bottom: 1px solid #dcdee3; } #card-master .select.opened > ul > li:hover { background: #f7f7f7; } `; main.appendChild(style); document.addEventListener('click', (e) => { if (!select.contains(e.target)) { select.classList.remove('opened'); } }); form.before(main); if (autoSelect) { autoSelect.click(); } }; setInterval(() => { init(); }, 500); })();