Last active
July 4, 2023 13:09
-
-
Save longnull/7bb9f946f3bba27a7d1df272291eef9f to your computer and use it in GitHub Desktop.
AliExpress Address Randomizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name AliExpress Address Randomizer | |
// @description Рандомизирует данные по заданному шаблону и заполняет форму адреса доставки | |
// @author longnull | |
// @namespace longnull | |
// @version 1.2.1 | |
// @updateURL https://gist.github.com/longnull/7bb9f946f3bba27a7d1df272291eef9f/raw/AliExpressAddressRandomizer.user.js | |
// @downloadURL https://gist.github.com/longnull/7bb9f946f3bba27a7d1df272291eef9f/raw/AliExpressAddressRandomizer.user.js | |
// @match *://ilogisticsaddress.aliexpress.com/addressList.htm* | |
// @match *://ilogisticsaddress.aliexpress.ru/addressList.htm* | |
// @match *://shoppingcart.aliexpress.com/orders.htm* | |
// @match *://shoppingcart.aliexpress.ru/orders.htm* | |
// @match *://shoppingcart.aliexpress.com/order/confirm_order.htm* | |
// @match *://shoppingcart.aliexpress.ru/order/confirm_order.htm* | |
// @match *://www.aliexpress.com/p/trade/confirm.html* | |
// @match *://aliexpress.ru/checkout* | |
// @match *://aliexpress.ru/address-list* | |
// @grant GM_xmlhttpRequest | |
// @connect worldnamegenerator.com | |
// @connect * | |
// @noframes | |
// ==/UserScript== | |
(() => { | |
'use strict'; | |
// ================================================================================================================================================================ | |
// Изменения | |
// ================================================================================================================================================================ | |
// v1.2.1 | |
// - Обновлён под актульный aliexpress.ru | |
// v1.2 | |
// - Обновлён под новый сайт aliexpress.ru (также нужно обновить скрипт для шаблонов - https://gist.github.com/longnull/7bb9f946f3bba27a7d1df272291eef9f/raw/AliExpressAddressRandomizerTemplates.user.js) | |
// - Добавлено отключение появления новой формы адреса на странице оформления (теперь не нужен отдельный скрипт) | |
// - Изменён стиль кнопок | |
// v1.1.8 | |
// - Добавлен новый адрес страницы оформления | |
// - Обновлён шаблон, который берёт данные с сайта | |
// ================================================================================================================================================================ | |
// Синтаксис шаблонов | |
// ================================================================================================================================================================ | |
// Генераторы | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// {1|2|3} | Один случайный элемент из перечисленных | |
// | Элементы разделяются символом "|", могут быть пустыми | |
// | | |
// | Примеры: | |
// | {Василий|Иван|Пётр} - Одно из трёх имён | |
// | {,|} - Запятая или ничего | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// [1|2|3| ] | Случайный порядок перечисленных элементов | |
// | Элементы разделяются символом "|", последний элемент - разделитель, который будет вставлен между элементами в сгенерированной строке | |
// | | |
// | Пример: | |
// | [1|2|3| ] - Случайный порядок цифр с пробелом между ними | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// %rs(len,chars)% | Случайная строка | |
// | | |
// | Параметры: | |
// | len - Длина строки. Если указан диапазон (например, "6-12"), то длина будет случайной в указанном диапазоне | |
// | chars (необязательный) - Наборы символов из которых будет сгенерирована строка. Значение по умолчанию - "zZ0" | |
// | Доступные наборы символов: | |
// | Английский алфавит в нижнем регистре - нужно указать любую букву английского алфавита в нижнем регистре | |
// | Английский алфавит в верхнем регистре - нужно указать любую букву английского алфавита в верхнем регистре | |
// | Русский алфавит в нижнем регистре - нужно указать любую букву русского алфавита в нижнем регистре | |
// | Русский алфавит в верхнем регистре - нужно указать любую букву русского алфавита в верхнем регистре | |
// | Цифры - нужно указать любую цифру | |
// | Примеры: | |
// | %rs(10,zZ0)% - Строка длиной в 10 символов, в которой могут быть буквы английского алфавита в обоих регистрах и цифры | |
// | %rs(10,яЯ)% - Строка длиной в 10 символов, в которой могут быть буквы русского алфавита в обоих регистрах | |
// | %rs(6-12,0)% - Строка со случайной длиной от 6 до 12 символов, в которой могут быть только цифры | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// %rn(range,len)% | Случайное число | |
// | | |
// | Параметры: | |
// | range - Диапазон случайного числа в виде "1-100" | |
// | len (необязательный) - Дополнение нулями до указанной длины | |
// | Примеры: | |
// | %rn(1000-10000)% - Случайное число от 1000 до 10000 | |
// | %rn(1-12,2)% - Случайное число от 1 до 12 дополненное нулём | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// %rw(len,cap)% | Случайное "слово" (чередуемые согласные и гласные буквы английского алфавита) | |
// | Примеры "слов": Buvemu, Ditubituj, Humanena, Cihoho | |
// | | |
// | Параметры: | |
// | len - Длина "слова". Если указан диапазон (например, "6-12"), то длина будет случайной в указанном диапазоне | |
// | cap (необязательный) - Если указано "1" или "true", то первая буква будет заглавной. Значение по умолчанию - не делать заглавной | |
// | Примеры: | |
// | %rw(10)% - Случайное "слово" длиной в 10 символов | |
// | %rw(6-10,1)% - Случайное "слово" со случайной длиной от 6 до 10 символов и начинающееся с заглавной буквы | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// %dt(format)% | Строка с элементами даты или времени | |
// | | |
// | Параметры: | |
// | format - Формат строки. Может содержать токены даты/времени и другие символы. Значение по умолчанию - "dd.MM.yyyy" | |
// | Список доступных токенов: | |
// | yy - Год из двух цифр | |
// | yyyy - Год из четырёх цифр | |
// | M - Месяц не дополняемый нулём | |
// | MM - Месяц дополняемый нулём | |
// | MMM - Короткое название месяца (на английском) | |
// | MMMM - Полное название месяца (на английском) | |
// | EEE - Короткое название дня недели (на английском) | |
// | EEEE - Полное название дня недели (на английском) | |
// | d - День не дополняемый нулём | |
// | dd - День дополняемый нулём | |
// | h - Час в 12-часовом формате не дополняемый нулём | |
// | hh - Час в 12-часовом формате дополняемый нулём | |
// | H - Час в 24-часовом формате не дополняемый нулём | |
// | HH - Час в 24-часовом формате дополняемый нулём | |
// | m - Минута не дополняемая нулём | |
// | mm - Минута дополняемая нулём | |
// | aaa - AM/PM | |
// | s - Секунда не дополняемая нулём | |
// | ss - Секунда дополняемая нулём | |
// | S - Миллисекунда | |
// | Примеры: | |
// | %dt(ddMMyyyyHHmm)% - Строка вида "010120201200" | |
// | %dt(dd.MM.yyyy HH:mm)% - Строка вида "01.01.2020 12:00" | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// Сохранение и повторное использование значений из генераторов | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// $имя | Сохранение сгенерированной строки в переменную с указанным именем | |
// | | |
// | Примеры: | |
// | {1|2|3$цифра} | |
// | [Pupkin|Vasiliy|Vasilevich| $фио] | |
// | %rs(6-12)$символы% | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// %имя% | Значение переменной с указанным именем | |
// | При использовании переменных стоит учитывать порядок обработки полей шаблона, т.к. переменную нельзя использовать раньше, чем она была создана | |
// | Поля шаблона при генерации обрабатываются в таком порядке: country, province, city, name, address, address2, zip, mobileCode, mobile, extra | |
// | | |
// | Пример: | |
// | %фио% | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// Прочее | |
// -------------------------------------------------------------------------------- | |
// * | Выбрать случайный элемент в выпадающем списке (для полей country, province, city) | |
// | | |
// | Пример: | |
// | province: '*' | |
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
// ================================================================================================================================================================ | |
// Шаблоны | |
// ================================================================================================================================================================ | |
// Чтобы добавить шаблон, нужно скопировать имеющийся шаблон от метки начала до метки конца, вставить рядом с другим шаблоном (порядок кнопок будет таким же) и изменить имя | |
// Всегда храните свои шаблоны в отдельном файле, не храните их только в скрипте, иначе потеряете их при обновлении скрипта | |
// Все поля в шаблонах необязательные | |
// | |
// Шаблоны можно вставить в отдельный скрипт, чтобы не приходилось их вставлять обратно в основной скрипт после обновлений | |
// Скрипт для вставки шаблонов: https://gist.github.com/longnull/7bb9f946f3bba27a7d1df272291eef9f/raw/AliExpressAddressRandomizerTemplates.user.js | |
// Иногда может быть необходимо обновить скрипт с шаблонами, если, например, в основном скрипте изменились @match строки вверху (они должны совпадать) | |
let templates = { | |
// --- Начало шаблона --- | |
// Имя шаблона. Используется для подписей кнопок и в логике скрипта, не должно повторяться | |
'BY': { | |
// ФИО | |
// В данном примере используется переменная из поля variables | |
name: '%фио%', | |
// Страна | |
country: 'Belarus', | |
// Область/регион/штат | |
// Может быть строкой, массивом и объектом с отдельными значениями для текстового поля и для выпадающего списка (см. примеры ниже) | |
// В данном примере используется переменная из поля variables | |
province: '%город%ska{y|j}a oblast', | |
// Город | |
// Может быть строкой, массивом и объектом с отдельными значениями для текстового поля и для выпадающего списка (см. примеры ниже) | |
// В данном примере используется переменная из поля variables | |
city: '%город%', | |
// Адрес | |
address: '{Belarus, %город%, |%город%, |}ul{.|ica|itsa|} Arbuzna{y|j}a{,| -|$dlm} d{.|om} {n|n.|no|no.|nomer|} 9%dlm% k{v|w}{.|ar|ar.|art|art.|artira} {n|n.|no|no.|nomer|} 99%dlm% %rs(6-12,a)%', | |
// Адрес 2 | |
address2: '%rs(6-10,a)%', | |
// Индекс | |
zip: '234567', | |
// Код страны | |
mobileCode: '+375', | |
// Номер телефона | |
mobile: '%rn(70-99)%%rn(1111111-9999999)%', | |
// Переменные шаблона | |
// Переменные шаблона перезаписывают глобальные переменные с таким же именем | |
// Значения присваиваются до заполнения каких-либо полей, соответственно, переменные можно использовать в любом поле (%город%) | |
variables: { | |
'фио': '[Pupkin|Vasili{y|i}|Vasil{i|}evich| ]', | |
'город': '{Gomel|Homel|Homiel|Homyel}', | |
}, | |
// true - Выбрать адресом по умолчанию | |
// false - Не выбирать | |
default: true, | |
// true - Нажать кнопку сохранения адреса | |
// false - Не нажимать | |
save: false | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
'RU': { | |
name: '{[Пупкин|Василий|Васильевич| ]|[Иванов|Иван|Иванович| ]}', | |
country: 'Russian Federation', | |
province: 'Moskva g', | |
city: 'Moskva g', | |
address: 'ул{ица|.|} Арбузная{,| -|$dlm} д{.|ом|} 7%dlm% кв{.|артира|} 77 %rs(6-12,a)%', | |
address2: '%rs(6-12,a)%', | |
zip: '123456', | |
mobileCode: '+7', | |
mobile: '%rn(1111111111-9999999999)%', | |
// Информация для таможни (не обязательно) | |
extra: [ | |
{ | |
selector: '#lastName', | |
value: 'Пупкин' | |
}, | |
{ | |
selector: '#firstName', | |
value: 'Василий' | |
}, | |
{ | |
selector: '#middleName', | |
value: 'Васильевич' | |
}, | |
{ | |
selector: '#passportNo', | |
value: '%rn(1111111111-9999999999)%' | |
}, | |
{ | |
selector: '#passportNoDate input', | |
value: '%rn(1-28,2)%-%rn(1-12,2)%-%rn(2000-2010)%' | |
}, | |
{ | |
selector: '#passportOrganization', | |
value: 'Кем-то' | |
}, | |
{ | |
selector: '#birthday input', | |
value: '%rn(1-28,2)%-%rn(1-12,2)%-%rn(1980-1990)%' | |
}, | |
], | |
default: true, | |
save: false | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
'UA': { | |
name: '%фио%', | |
country: 'Ukraine', | |
province: 'Kyiv', | |
city: 'Kyiv', | |
address: 'ul{.|ica|itsa|} Arbuzna{y|j}a, d{.|om} 11, kv{.|ar.|art.|artira} 99', | |
address2: '%rs(4-6,a)%', | |
zip: '%rn(11111-88888)%', | |
mobileCode: '+380', | |
mobile: '%rn(1111111111-9999999999)%', | |
default: false, | |
save: false | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
'US (rnd)': { | |
name: '%rw(4-8,1)% %rw(6-10,1)%', | |
country: 'United States', | |
province: '*', | |
city: { | |
// Значение для текстового поля | |
text: '%rw(6-12,1)%', | |
// Значение для выпадающего списка | |
select: '*' | |
}, | |
address: '%rs(10-16,a0)%', | |
address2: '%rs(6-12,a)%', | |
zip: '%rn(11111-99999)%', | |
mobile: '%rn(1111111111-9999999999)%', | |
default: false, | |
save: false | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
// Пример шаблона, который берёт данные с worldnamegenerator.com | |
'US (wng)': async () => { | |
const response = await httpRequest({ | |
url: 'https://www.worldnamegenerator.com/usa_address_generator' | |
}); | |
if (response.status !== 200) { | |
return false; | |
} | |
const html = document.createElement('html'); | |
html.innerHTML = response.responseText; | |
const items = html.querySelectorAll('.common-table tr td:nth-child(2)'); | |
if (!items.length) { | |
return false; | |
} | |
const tmp = { | |
name: items[0].textContent.trim().replace(/\u00a0/g, ' ').replace(/\s{2,}/g, ' '), | |
country: 'United States', | |
province: items[9].textContent.trim(), | |
city: items[7].textContent.trim(), | |
address: items[6].textContent.trim().replace(/\u00a0/g, ' ').replace(/\s{2,}/g, ' '), | |
zip: items[10].textContent.trim(), | |
mobile: items[12].textContent.trim().replace(/\D/g, '') | |
}; | |
html.remove(); | |
return tmp; | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
// Шаблон для aliexpress.ru | |
'Россия': { | |
name: 'Иванов Иван Иванович', | |
country: ['Россия', 'Russian Federation', 'Rossiya'], | |
province: 'Москва г', | |
city: 'Москва г', | |
address: 'ул. Арбузная, дом 7, квартира 77', | |
zip: '123456', | |
mobileCode: '+7', | |
mobile: '%rn(1111111111-9999999999)%', | |
default: true, | |
save: false, | |
// true - шаблон будет отображаться на aliexpress.ru, иначе только на aliexpress.com | |
ru: true | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
// Шаблон для aliexpress.ru | |
'Беларусь': { | |
name: '%фио%', | |
country: ['Беларусь', 'Belarus'], | |
province: 'Minsk', | |
city: 'Minsk', | |
address: 'ul. Arbuznaya, dom 9, kvartira 99', | |
zip: '234567', | |
mobileCode: '+375', | |
mobile: '%rn(111111111-999999999)%', | |
default: true, | |
save: false, | |
ru: true | |
}, | |
// --- Конец шаблона --- | |
// --- Начало шаблона --- | |
// Шаблон для aliexpress.ru | |
'Узбекистан': { | |
name: '%фио%', | |
country: ['Узбекистан', 'Uzbekistan', 'O\'zbekiston'], | |
province: 'Toshkent', | |
city: 'Bektemir Tumani', | |
address: 'ul. Arbuznaya, dom 9, kvartira 99', | |
zip: '100033', | |
mobileCode: '+998', | |
mobile: '%rn(111111111-999999999)%', | |
default: true, | |
save: false, | |
ru: true | |
}, | |
// --- Конец шаблона --- | |
}; | |
// ================================================================================================================================================================ | |
// Глобальные переменные | |
// ================================================================================================================================================================ | |
// Переменные, которые доступны в любом шаблоне | |
const variables = { | |
// 'имя': 'значение' | |
'фио': '[Ivanov|Ivan|Ivanovich| ]', | |
}; | |
// ================================================================================================================================================================ | |
// Настройки | |
// ================================================================================================================================================================ | |
const config = { | |
// Отключение появления новой формы адреса на странице оформления | |
disableNewAddressForm: true, | |
// Режим отладки | |
debug: false | |
}; | |
// ================================================================================================================================================================ | |
const httpRequest = (params) => { | |
return new Promise((resolve) => { | |
params.timeout = 30000; | |
params.onload = resolve; | |
params.onerror = resolve; | |
params.ontimeout = resolve; | |
params.onabort = resolve; | |
if (!params.method) { | |
params.method = 'GET'; | |
} | |
const func = typeof GM.xmlHttpRequest !== 'undefined' ? GM.xmlHttpRequest : GM_xmlhttpRequest; | |
func(params); | |
}); | |
}; | |
const log = { | |
debug(...args) { | |
if (config && config.debug) { | |
const ar = Array.prototype.slice.call(args, 0); | |
ar.unshift('AAR ::'); | |
console.log(...ar); | |
} | |
} | |
}; | |
const sleep = async (ms) => { | |
return new Promise((resolve) => { | |
setTimeout(resolve, ms); | |
}); | |
}; | |
const waitForFetch = (url, startTimeout = 0, endTimeout = 0, max = 0) => { | |
log.debug('waitForFetch : url =', url, ', startTimeout =', startTimeout, ', endTimeout =', endTimeout, ', max =', max); | |
if (!Array.isArray(url)) { | |
url = [url]; | |
} | |
const fetchOrig = unsafeWindow.fetch; | |
const promise = new Promise((resolve) => { | |
let startTimer; | |
let endTimer; | |
let requestCount = 0; | |
let responseCount = 0; | |
let urlIndex = 0; | |
const resolveAndRestore = () => { | |
log.debug('waitForFetch : resolveAndRestore :', url); | |
resolve(); | |
unsafeWindow.fetch = fetchOrig; | |
}; | |
unsafeWindow.fetch = async (resource, options) => { | |
log.debug('waitForFetch : request :', resource); | |
if (!resource.includes(url[urlIndex])) { | |
return fetchOrig(resource, options); | |
} | |
requestCount++; | |
urlIndex++; | |
if (startTimer) { | |
clearTimeout(startTimer); | |
startTimer = null; | |
} | |
if (endTimer) { | |
clearTimeout(endTimer); | |
endTimer = null; | |
} | |
log.debug('waitForFetch : request match :', resource); | |
return fetchOrig(resource, options).then((response) => { | |
log.debug('waitForFetch : response :', response); | |
responseCount++; | |
if (!endTimeout || (max && responseCount >= max) || (requestCount == responseCount && urlIndex >= url.length)) { | |
log.debug('waitForFetch : finish (if)'); | |
resolveAndRestore(); | |
} else if (endTimeout && requestCount == responseCount) { | |
endTimer = setTimeout(() => { | |
log.debug('waitForFetch : finish (endTimeout)'); | |
resolveAndRestore(); | |
}, endTimeout); | |
} | |
return response; | |
}).catch(() => { | |
log.debug('waitForFetch : request error :', url); | |
resolveAndRestore(); | |
}); | |
}; | |
if (startTimeout) { | |
startTimer = setTimeout(() => { | |
log.debug('waitForFetch : finish (startTimeout)'); | |
resolveAndRestore(); | |
}, startTimeout); | |
} | |
}); | |
promise.catch(() => { | |
log.debug('waitForFetch : catch :', url); | |
unsafeWindow.fetch = fetchOrig; | |
}); | |
return promise; | |
} | |
const waitForJsonp = (url, startTimeout = 0, endTimeout = 0, max = 0) => { | |
log.debug('waitForJsonp : url =', url, ', startTimeout =', startTimeout, ', endTimeout =', endTimeout, ', max =', max); | |
return new Promise((resolve) => { | |
let startTimer; | |
let endTimer; | |
let callbacksCount = 0; | |
let matchCount = 0; | |
const results = []; | |
const observer = new MutationObserver((mutations) => { | |
for (const m of mutations) { | |
for (const n of m.addedNodes) { | |
if (!n.src) { | |
continue; | |
} | |
if (Array.isArray(url)) { | |
let found = false; | |
for (const u of url) { | |
if (n.src.includes(u)) { | |
found = true; | |
break; | |
} | |
} | |
if (!found) { | |
continue; | |
} | |
} else if (!n.src.includes(url)) { | |
continue; | |
} | |
const match = n.src.match(/[?|&](?:callback|cb)=([a-zA-Z0-9_\-]+)/); | |
if (match) { | |
matchCount++; | |
log.debug('waitForJsonp : match : matchCount =', matchCount, ', url =', url, ', match =', match); | |
if (startTimer) { | |
clearTimeout(startTimer); | |
startTimer = null; | |
} | |
if (endTimer) { | |
clearTimeout(endTimer); | |
endTimer = null; | |
} | |
const originalCallback = unsafeWindow[match[1]]; | |
unsafeWindow[match[1]] = (obj) => { | |
callbacksCount++; | |
log.debug('waitForJsonp : callback : matchCount =', matchCount, ', callbacksCount =', callbacksCount, ', obj =', obj); | |
originalCallback(obj); | |
results.push(obj); | |
if (!endTimeout || (max && callbacksCount >= max)) { | |
log.debug('waitForJsonp : finish (if)'); | |
observer.disconnect(); | |
return resolve(results); | |
} | |
if (matchCount === callbacksCount) { | |
endTimer = setTimeout(() => { | |
log.debug('waitForJsonp : finish (endTimeout)'); | |
observer.disconnect(); | |
resolve(results); | |
}, endTimeout); | |
} | |
}; | |
} | |
} | |
} | |
}); | |
observer.observe(document.documentElement, { | |
childList: true, | |
subtree: true | |
}); | |
if (startTimeout) { | |
startTimer = setTimeout(() => { | |
log.debug('waitForJsonp : finish (startTimeout)'); | |
observer.disconnect(); | |
resolve(results); | |
}, startTimeout); | |
} | |
}); | |
}; | |
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 ''; | |
} | |
let rnd = `${randomRange(min, max)}`; | |
if (params.length > 1 && params[1].search(/^\d+$/) > -1) { | |
rnd = rnd.padStart(parseInt(params[1]), '0'); | |
} | |
return rnd; | |
}, | |
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 refreshInput = (input) => { | |
if (typeof input === 'string') { | |
input = document.querySelector(input); | |
} | |
if (!input) { | |
return; | |
} | |
let props = Object.keys(input).filter((i) => i.indexOf('__reactEventHandlers') >= 0); | |
if (!props.length) { | |
props = Object.keys(input).filter((i) => i.indexOf('__reactProps') >= 0); | |
} | |
if (props.length) { | |
if (input[props[0]].onChange) { | |
input[props[0]].onChange({target: {value: input.value}}); | |
} | |
if (input[props[0]].onKeyDown) { | |
input[props[0]].onKeyDown({target: {value: input.value}}); | |
} | |
if (input[props[0]].onBlur) { | |
input[props[0]].onBlur({}); | |
} | |
} | |
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 setInput = (input, value) => { | |
if (typeof input === 'string') { | |
input = document.querySelector(input); | |
} | |
if (!input) { | |
return; | |
} | |
input.value = value; | |
refreshInput(input); | |
}; | |
const select = async (combo, currentItem, waitAfterClick, items, itemToSelect) => { | |
const compareItem = (item) => { | |
item = item.trim().toLowerCase(); | |
if (Array.isArray(itemToSelect)) { | |
for (const i of itemToSelect) { | |
if (i.toLowerCase() === item) { | |
return i; | |
} | |
} | |
} else if (itemToSelect.toLowerCase() === item) { | |
return itemToSelect; | |
} | |
return false; | |
}; | |
log.debug('select : combo =', combo, ', currentItem =', currentItem, ', waitAfterClick =', waitAfterClick, ', items =', items, ', itemToSelect =', itemToSelect); | |
if (combo) { | |
const comboElement = typeof combo === 'string' ? document.querySelector(combo) : combo; | |
if (!comboElement) { | |
log.debug('select : combo element not found'); | |
return null; | |
} | |
const currentItemElement = currentItem.startsWith('>') ? comboElement.querySelector(currentItem.replace(/^>\s*/, '')) : document.querySelector(currentItem); | |
let currentItemValue; | |
if (currentItemElement) { | |
if (currentItemElement.nodeName === 'INPUT') { | |
currentItemValue = currentItemElement.value; | |
} else { | |
currentItemValue = currentItemElement.innerText.trim(); | |
} | |
} | |
if (currentItemValue && itemToSelect !== '*' && compareItem(currentItemValue)) { | |
log.debug('select : already selected'); | |
return false; | |
} | |
log.debug('select : clicking combo'); | |
comboElement.click(); | |
log.debug(`select : waiting for "${waitAfterClick}"`); | |
await waitForElement(waitAfterClick.replace(/^>\s*/, ''), true, true, waitAfterClick.startsWith('>') ? comboElement : document); | |
} | |
const itemElements = items.startsWith('>') ? comboElement.querySelectorAll(items.replace(/^>\s*/, '')) : document.querySelectorAll(items); | |
if (!itemElements.length) { | |
log.debug('select : items not found'); | |
return null; | |
} | |
if (itemToSelect === '*') { | |
log.debug('select : clicking random item'); | |
const idx = Math.floor(Math.random() * itemElements.length); | |
itemElements[idx].click(); | |
return itemElements[idx]; | |
} | |
for (const i of itemElements) { | |
const match = compareItem(i.innerText) | |
if (match) { | |
log.debug(`select : clicking : ${match}`); | |
i.click(); | |
return i; | |
} | |
} | |
log.debug('select : item not found'); | |
}; | |
const fill = (template) => { | |
const fillAfterCountry = async () => { | |
if (template.province) { | |
if (isAliexpressRu) { | |
if (document.querySelector('input[name="provinceSearch"]')) { | |
log.debug('fill : province is an input with suggestions'); | |
const el = document.querySelector('input[name="provinceSearch"]'); | |
let province = typeof template.province === 'object' && !Array.isArray(template.province) ? template.province.select : template.province; | |
if (Array.isArray(province)) { | |
province = province.map((v) => randomizeText(v, vars)); | |
} else { | |
province = randomizeText(province, vars); | |
} | |
if ((Array.isArray(province) && province.includes(el.value)) || (!Array.isArray(province) && province === el.value)) { | |
log.debug('fill : province already selected'); | |
} else { | |
log.debug('fill : province :', province); | |
const provinceItemElement = await select( | |
'div[class*="AddressFormWidget_Section__container"] > div[class*="AddressFormWidget_Select__root"]:nth-child(2) div[id*="toggle-button"]', | |
'input[name="provinceSearch"]', | |
'div[class*="AddressFormWidget_Section__container"] > div[class*="AddressFormWidget_Select__root"]:nth-child(2) ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"]', | |
'div[class*="AddressFormWidget_Section__container"] > div[class*="AddressFormWidget_Select__root"]:nth-child(2) ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"] li', | |
province | |
); | |
if (provinceItemElement === null) { | |
log.debug('fill : province select error'); | |
return; | |
} | |
} | |
} else { | |
log.debug('fill : province is an input'); | |
let province = typeof template.province === 'object' && !Array.isArray(template.province) ? template.province.text : template.province; | |
province = randomizeText(Array.isArray(province) ? province[0] : province, vars); | |
log.debug('fill : province :', province); | |
setInput('input[name="province"]', province); | |
} | |
} else { | |
if (document.querySelector(`.input-default.country .selector-item:not(:last-child) .next-select`)) { | |
log.debug('fill : province is a combobox'); | |
await waitForElement(`.input-default.country .selector-item:not(:last-child) .next-select .next-select-inner:not(.next-disabled)`); | |
let province = typeof template.province === 'object' && !Array.isArray(template.province) ? template.province.select : template.province; | |
if (Array.isArray(province)) { | |
province = province.map((v) => randomizeText(v, vars)); | |
} else { | |
province = randomizeText(province, vars); | |
} | |
log.debug('fill : province :', province); | |
const provinceItemElement = await select( | |
`.input-default.country .selector-item:not(:last-child) .next-select`, | |
'> em', | |
`.input-default.country .selector-item:not(:last-child) .next-select-popup-wrap .dropdown-content .next-menu-item`, | |
`.input-default.country .selector-item:not(:last-child) .next-select-popup-wrap .dropdown-content .next-menu-item`, | |
province | |
); | |
if (provinceItemElement === null) { | |
log.debug('fill : province select error'); | |
return; | |
} | |
await sleep(300); | |
await waitForElement(`.input-default.country .selector-item:last-child .next-input:not(.next-disabled)`); | |
} else { | |
log.debug('fill : province is not a combobox'); | |
await waitForElement(`.input-default.country .selector-item:not(:last-child) .next-input:not(.next-disabled)`); | |
let province = typeof template.province === 'object' && !Array.isArray(template.province) ? template.province.text : template.province; | |
province = randomizeText(Array.isArray(province) ? province[0] : province, vars); | |
log.debug('fill : province :', province); | |
setInput(`.input-default.country .selector-item:not(:last-child) .next-input input`, province); | |
} | |
} | |
} else if (template.country) { | |
const provinceInputElement = document.querySelector(`.input-default.country .selector-item:not(:last-child) .next-input input`); | |
if (provinceInputElement) { | |
if (currentProvince) { | |
setInput(provinceInputElement, ''); | |
setInput(provinceInputElement, currentProvince); | |
} | |
} | |
} | |
if (template.city) { | |
if (isAliexpressRu) { | |
if (document.querySelector('input[name="citySearch"]')) { | |
log.debug('fill : city is an input with suggestions'); | |
const el = document.querySelector('input[name="citySearch"]'); | |
let city = typeof template.city === 'object' && !Array.isArray(template.city) ? template.city.select : template.city; | |
if (Array.isArray(city)) { | |
city = city.map((v) => randomizeText(v, vars)); | |
} else { | |
city = randomizeText(city, vars); | |
} | |
if ((Array.isArray(city) && city.includes(el.value)) || (!Array.isArray(city) && city === el.value)) { | |
log.debug('fill : city already selected'); | |
} else { | |
log.debug('fill : city :', city); | |
await Promise.all([ | |
await waitForFetch('city-suggests', 1000, 1000, 1), | |
setInput('input[name="citySearch"]', city) | |
]); | |
await sleep(500); | |
document.querySelector('div[class*="AddressFormWidget_Section__container"] > div[class*="AddressFormWidget_Select__root"]:nth-child(3) div[id*="toggle-button"]').click(); | |
await sleep(500); | |
const cityItemElement = await select( | |
'', '', '', | |
'div[class*="AddressFormWidget_Section__container"] > div[class*="AddressFormWidget_Select__root"]:nth-child(3) ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"] li', | |
city | |
); | |
if (cityItemElement === null) { | |
log.debug('fill : city select error'); | |
return; | |
} | |
} | |
} else { | |
log.debug('fill : city is an input'); | |
let city = typeof template.city === 'object' && !Array.isArray(template.city) ? template.city.text : template.city; | |
city = randomizeText(Array.isArray(city) ? city[0] : city, vars); | |
log.debug('fill : city :', city); | |
setInput('input[name="city"]', city); | |
} | |
} else { | |
if (document.querySelector(`.input-default.country .selector-item:last-child .next-select`)) { | |
log.debug('fill : city is a combobox'); | |
let city = typeof template.city === 'object' && !Array.isArray(template.city) ? template.city.select : template.city; | |
if (Array.isArray(city)) { | |
city = city.map((v) => randomizeText(v, vars)); | |
} else { | |
city = randomizeText(city, vars); | |
} | |
log.debug('fill : city :', city); | |
await waitForElement(`.input-default.country .selector-item:last-child .next-select .next-select-inner:not(.next-disabled)`); | |
const citySearchElement = document.querySelector(`.input-default.country .selector-item:last-child .next-search input`) | |
if (citySearchElement) { | |
setInput(citySearchElement, ''); | |
} | |
const cityItemElement = await select( | |
`.input-default.country .selector-item:last-child .next-select`, | |
'> em', | |
`.input-default.country .selector-item:last-child .next-select-popup-wrap .dropdown-content .next-menu-item:nth-child(2)`, | |
`.input-default.country .selector-item:last-child .next-select-popup-wrap .dropdown-content .next-menu-item`, | |
city | |
); | |
if (cityItemElement === null) { | |
log.debug('fill : city select error'); | |
return; | |
} | |
} else { | |
log.debug('fill : city is not a combobox'); | |
await waitForElement(`.input-default.country .selector-item:last-child .next-input:not(.next-disabled)`); | |
let city = typeof template.city === 'object' && !Array.isArray(template.city) ? template.city.text : template.city; | |
city = randomizeText(Array.isArray(city) ? city[0] : city, vars); | |
log.debug('fill : city :', city); | |
setInput(`.input-default.country .selector-item:last-child .next-input input`, city); | |
} | |
} | |
} else if (template.country) { | |
const cityInputElement = document.querySelector(`.input-default.country .selector-item:last-child .next-input input`); | |
if (cityInputElement) { | |
if (currentCity) { | |
setInput(cityInputElement, ''); | |
setInput(cityInputElement, currentCity); | |
} | |
} | |
} | |
if (template.name) { | |
log.debug('fill : name'); | |
setInput('input[name="contactPerson"], #contactPerson', randomizeText(template.name, vars)); | |
} else if (template.country) { | |
refreshInput('input[name="contactPerson"], #contactPerson'); | |
} | |
if (template.address) { | |
log.debug('fill : address'); | |
setInput('input[name="address"], input[name="addressSearch"], #address', randomizeText(template.address, vars)); | |
} else if (template.country) { | |
refreshInput('input[name="address"], input[name="addressSearch"], #address'); | |
} | |
if (template.address2) { | |
log.debug('fill : address2'); | |
setInput('#address2', randomizeText(template.address2, vars)); | |
} else if (template.country) { | |
refreshInput('#address2'); | |
} | |
if (template.zip) { | |
const zip = randomizeText(template.zip, vars); | |
if (isAliexpressRu) { | |
if (document.querySelector('input[name="zipSearch"]')) { | |
log.debug('fill : zip is an input with suggestions'); | |
log.debug('fill : zip :', zip); | |
const zipItemElement = await select( | |
'div[class*="AddressFormWidget_Section__container"] div[class*="AddressFormWidget_Form__zipInput"] div[id*="toggle-button"]', | |
'input[name="zipSearch"]', | |
'div[class*="AddressFormWidget_Section__container"] div[class*="AddressFormWidget_Form__zipInput"] ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"]', | |
'div[class*="AddressFormWidget_Section__container"] div[class*="AddressFormWidget_Form__zipInput"] ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"] li', | |
zip | |
); | |
if (zipItemElement === null) { | |
log.debug('fill : zip select error'); | |
return; | |
} | |
} else { | |
log.debug('fill : zip is an input'); | |
setInput('input[name="zip"]', zip); | |
} | |
} else { | |
setInput('#zip', zip); | |
} | |
} else if (template.country) { | |
refreshInput('input[name="zip"], input[name="zipSearch"], #zip'); | |
} | |
if (template.mobileCode) { | |
log.debug('fill : mobileCode'); | |
setInput('input[name="phoneCountry"], #phoneCountry', randomizeText(template.mobileCode, vars)); | |
} else if (template.country) { | |
refreshInput('input[name="phoneCountry"], #phoneCountry'); | |
} | |
if (template.mobile) { | |
log.debug('fill : mobile'); | |
setInput('input[name="mobileNo"], input[name="phoneNumber"], #mobileNo', randomizeText(template.mobile, vars)); | |
} else if (template.country) { | |
refreshInput('input[name="mobileNo"], input[name="phoneNumber"]'); | |
} | |
if (Array.isArray(template.extra)) { | |
log.debug('fill : extra'); | |
for (const e of template.extra) { | |
if (e.selector && typeof e.value !== 'undefined') { | |
const element = document.querySelector(e.selector); | |
if (element) { | |
if (element.querySelector('.next-select')) { | |
log.debug(`fill : extra "${e.selector}" is a combobox`); | |
await select( | |
`${e.selector} .next-select`, | |
'> em', | |
`${e.selector} .next-select-popup-wrap .dropdown-content`, | |
`${e.selector} .next-select-popup-wrap .dropdown-content .next-menu-item`, | |
randomizeText(e.value, vars) | |
); | |
} if (element.type === 'checkbox' || element.querySelector('input[type="checkbox"]')) { | |
log.debug(`fill : extra "${e.selector}" is a checkbox`); | |
const input = element.type === 'checkbox' ? element : element.querySelector('input[type="checkbox"]'); | |
if (input.checked != !!e.value) { | |
input.click(); | |
} | |
} else { | |
log.debug(`fill : extra "${e.selector}" is probably a text field`); | |
setInput(element.nodeName === 'INPUT' ? element : element.querySelector('input'), randomizeText(e.value, vars)); | |
} | |
} | |
} | |
} | |
} | |
let el = document.querySelector('.ship-info .term-wrap .next-checkbox input'); | |
if (el && !el.checked) { | |
log.debug('fill : clicking terms'); | |
el.click(); | |
} | |
if (typeof template.default === 'boolean') { | |
el = document.querySelector('.ship-info > div:not(.term-wrap) .next-checkbox input, label[class*="AddressList_AddressForm__checkbox"] input'); | |
if (el && el.checked != template.default) { | |
log.debug('fill : clicking default'); | |
el.click(); | |
} | |
} | |
if (template.save) { | |
await sleep(200); | |
el = document.querySelector('.ship-info .address-save .next-btn-primary, div[class*="CheckoutShippingMethod_AddressForm__controls"] button[type="submit"], div[class*="AddressList_Header__header"] button[type="submit"]'); | |
if (el) { | |
log.debug('fill : clicking save'); | |
el.click(); | |
} | |
} | |
}; | |
const vars = {}; | |
let currentProvince; | |
let currentCity; | |
if (typeof variables === 'object') { | |
log.debug('fill : global variables'); | |
for (const v in variables) { | |
vars[v] = randomizeText(variables[v], vars); | |
} | |
} | |
if (typeof template.variables === 'object') { | |
log.debug('fill : template variables'); | |
for (const v in template.variables) { | |
vars[v] = randomizeText(template.variables[v], vars); | |
} | |
} | |
return new Promise(async (resolve) => { | |
if (typeof template === 'function') { | |
let funcRes = template(); | |
if (funcRes instanceof Promise) { | |
funcRes = await funcRes; | |
} | |
if (typeof funcRes !== 'object') { | |
return resolve(); | |
} | |
template = funcRes; | |
} | |
if (!template.country) { | |
log.debug('fill : country is not defined'); | |
await fillAfterCountry(); | |
return resolve(); | |
} | |
let currentCountry; | |
let countryToSelect; | |
if (isAliexpressRu) { | |
currentCountry = document.querySelector('input[name="countrySearch"]').value; | |
} else { | |
currentCountry = document.querySelector('div:not([style*="display: none"]) > .list_country .next-select .country-name, div:not([style*="display: none"]) > .country_for_select .next-select .country-name').textContent.trim(); | |
} | |
if (Array.isArray(template.country)) { | |
countryToSelect = template.country.map((v) => randomizeText(v, vars)); | |
} else { | |
countryToSelect = randomizeText(template.country, vars); | |
} | |
if ((Array.isArray(countryToSelect) && countryToSelect.includes(currentCountry)) || | |
(!Array.isArray(countryToSelect) && countryToSelect === currentCountry) | |
) { | |
log.debug('fill : country already selected :', countryToSelect); | |
await fillAfterCountry(); | |
return resolve(); | |
} | |
const provinceInputElement = document.querySelector(`.input-default.country .selector-item:not(:last-child) .next-input input`); | |
if (provinceInputElement) { | |
currentProvince = provinceInputElement.value; | |
} | |
const cityInputElement = document.querySelector(`.input-default.country .selector-item:last-child .next-input input`); | |
if (cityInputElement) { | |
currentCity = cityInputElement.value; | |
} | |
if (isAliexpressRu) { | |
waitForFetch('province-suggests', 1000, 500, 1).then(async () => { | |
await fillAfterCountry(); | |
return resolve(); | |
}); | |
} else { | |
waitForJsonp(['AjaxQueryCountries', 'AjaxQueryAddress'], 1000, 500).then(async () => { | |
await fillAfterCountry(); | |
return resolve(); | |
}); | |
} | |
log.debug('fill : selecting country : country =', countryToSelect); | |
let countryItemElement; | |
if (isAliexpressRu) { | |
countryItemElement = await select( | |
'div[class*="AddressFormWidget_SelectComponent__select"] div[id*="toggle-button"]', | |
'input[name="countrySearch"]', | |
'div[class*="AddressFormWidget_SelectComponent__select"] ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"]', | |
'div[class*="AddressFormWidget_SelectComponent__select"] ul[class*="AddressFormWidget_SelectComponent__dropdownMenuOpen"] li', | |
countryToSelect | |
); | |
} else { | |
countryItemElement = await select( | |
'div:not([style*="display: none"]) > .list_country .next-select, div:not([style*="display: none"]) > .country_for_select .next-select', | |
'div:not([style*="display: none"]) > .list_country .next-select .country-name, div:not([style*="display: none"]) > .country_for_select .next-select .country-name', | |
'div:not([style*="display: none"]) > .list_country .next-select-popup-wrap .dropdown-content, div:not([style*="display: none"]) > .country_for_select .next-select-popup-wrap .dropdown-content', | |
'div:not([style*="display: none"]) > .list_country .next-select-popup-wrap .dropdown-content .country-name, div:not([style*="display: none"]) > .country_for_select .next-select-popup-wrap .dropdown-content .country-name', | |
countryToSelect | |
); | |
} | |
if (countryItemElement === null) { | |
log.debug('fill : country select error'); | |
return resolve(); | |
} | |
}); | |
} | |
const init = () => { | |
log.debug('init'); | |
const enableButtons = (value) => { | |
if (value) { | |
buttonsContainer.classList.remove('disabled'); | |
spinner.style.display = 'none'; | |
} else { | |
buttonsContainer.classList.add('disabled'); | |
spinner.style.display = 'block'; | |
} | |
}; | |
const mainContainer = document.createElement('div'); | |
mainContainer.id = 'addr-rnd'; | |
mainContainer.innerHTML = ` | |
<div class="addr-rnd-buttons"></div> | |
<div class="addr-rnd-spinner"> | |
<div class="addr-rnd-spinner1 sk-child"></div> | |
<div class="addr-rnd-spinner2 sk-child"></div> | |
<div class="addr-rnd-spinner3 sk-child"></div> | |
<div class="addr-rnd-spinner4 sk-child"></div> | |
<div class="addr-rnd-spinner5 sk-child"></div> | |
<div class="addr-rnd-spinner6 sk-child"></div> | |
<div class="addr-rnd-spinner7 sk-child"></div> | |
<div class="addr-rnd-spinner8 sk-child"></div> | |
<div class="addr-rnd-spinner9 sk-child"></div> | |
<div class="addr-rnd-spinner10 sk-child"></div> | |
<div class="addr-rnd-spinner11 sk-child"></div> | |
<div class="addr-rnd-spinner12 sk-child"></div> | |
</div> | |
`; | |
const style = document.createElement('style'); | |
style.innerHTML = ` | |
#addr-rnd { | |
display: flex; | |
position: relative; | |
border-bottom: 1px solid #ccc; | |
margin-bottom: 10px; | |
} | |
#addr-rnd .addr-rnd-buttons { | |
flex-grow: 1; | |
} | |
#addr-rnd .addr-rnd-buttons .addr-rnd-btn { | |
margin: 0 4px 4px 0; | |
padding: 6px 10px; | |
height: 28px; | |
line-height: 14px; | |
color: #000; | |
background-color: #f2f2f2; | |
border: 1px solid #cacaca; | |
border-radius: 4px; | |
font-size: 12px; | |
position: relative; | |
display: inline-block; | |
box-shadow: none; | |
text-decoration: none; | |
text-align: center; | |
text-transform: none; | |
white-space: nowrap; | |
vertical-align: middle; | |
user-select: none; | |
transition: all .3s ease-out; | |
cursor: pointer; | |
} | |
#addr-rnd .addr-rnd-buttons .addr-rnd-btn:hover { | |
background-color: #f9f9f9; | |
} | |
#addr-rnd .addr-rnd-buttons.disabled { | |
pointer-events: none; | |
} | |
#addr-rnd .addr-rnd-buttons.disabled .addr-rnd-btn { | |
opacity: 0.5; | |
} | |
/* https://tobiasahlin.com/spinkit/ */ | |
#addr-rnd .addr-rnd-spinner { | |
display: none; | |
width: 26px; | |
height: 26px; | |
position: relative; | |
} | |
#addr-rnd .addr-rnd-spinner .sk-child { | |
width: 100%; | |
height: 100%; | |
position: absolute; | |
left: 0; | |
top: 0; | |
} | |
#addr-rnd .addr-rnd-spinner .sk-child:before { | |
content: ''; | |
display: block; | |
margin: 0 auto; | |
width: 15%; | |
height: 15%; | |
background-color: #333; | |
border-radius: 100%; | |
animation: addr-rnd-spinnerBounceDelay 1.2s infinite ease-in-out both; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner2 { | |
transform: rotate(30deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner3 { | |
transform: rotate(60deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner4 { | |
transform: rotate(90deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner5 { | |
transform: rotate(120deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner6 { | |
transform: rotate(150deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner7 { | |
transform: rotate(180deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner8 { | |
transform: rotate(210deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner9 { | |
transform: rotate(240deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner10 { | |
transform: rotate(270deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner11 { | |
transform: rotate(300deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner12 { | |
transform: rotate(330deg); | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner2:before { | |
animation-delay: -1.1s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner3:before { | |
animation-delay: -1s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner4:before { | |
animation-delay: -0.9s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner5:before { | |
animation-delay: -0.8s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner6:before { | |
animation-delay: -0.7s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner7:before { | |
animation-delay: -0.6s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner8:before { | |
animation-delay: -0.5s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner9:before { | |
animation-delay: -0.4s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner10:before { | |
animation-delay: -0.3s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner11:before { | |
animation-delay: -0.2s; | |
} | |
#addr-rnd .addr-rnd-spinner .addr-rnd-spinner12:before { | |
animation-delay: -0.1s; | |
} | |
@-webkit-keyframes addr-rnd-spinnerBounceDelay { | |
0%, 80%, 100% { | |
transform: scale(0); | |
} | |
40% { | |
transform: scale(1); | |
} | |
} | |
@keyframes addr-rnd-spinnerBounceDelay { | |
0%, 80%, 100% { | |
transform: scale(0); | |
} | |
40% { | |
transform: scale(1); | |
} | |
} | |
`; | |
mainContainer.appendChild(style); | |
if (unsafeWindow.aarTemplates) { | |
templates = unsafeWindow.aarTemplates; | |
} | |
const parent = document.querySelector('.ship-info, div[class*="AddressList_AddressList__addressForm"], div[class*="AddressFormWidget_ModalContainer"] div[class*="snow-dialog-v2_SnowDialogV2__closeBtn"] ~ div'); | |
const buttonsContainer = mainContainer.querySelector('.addr-rnd-buttons'); | |
const spinner = mainContainer.querySelector('.addr-rnd-spinner'); | |
const tmps = Object.keys(templates); | |
let filling = false; | |
for (const t of tmps) { | |
if ((isAliexpressRu && templates[t].ru !== true) || (!isAliexpressRu && templates[t].ru === true)) { | |
continue; | |
} | |
const btn = document.createElement('button'); | |
btn.className = 'addr-rnd-btn'; | |
btn.innerText = t; | |
btn.addEventListener('click', () => { | |
if (filling) { | |
return; | |
} | |
filling = true; | |
enableButtons(false); | |
fill(templates[t]).then(() => { | |
filling = false; | |
enableButtons(true); | |
}); | |
}); | |
buttonsContainer.appendChild(btn); | |
} | |
parent.prepend(mainContainer); | |
}; | |
const isAliexpressRu = location.host.includes('aliexpress.ru'); | |
let initialized = false; | |
log.debug('start'); | |
setInterval(() => { | |
const el = document.querySelector('.ship-info, input[class*="AddressFormWidget_SelectComponent__inputField"]'); | |
if (el) { | |
if (!initialized) { | |
initialized = true; | |
if (el.classList.contains('ship-info')) { | |
waitForElement('.ship-info .list_country .country-item').then(init); | |
} else { | |
init(); | |
} | |
} | |
} else { | |
initialized = false; | |
} | |
}, 500); | |
if (config.disableNewAddressForm && location.pathname == '/p/trade/confirm.html') { | |
setTimeout(() => { | |
if (!`${unsafeWindow.XMLHttpRequest}`.includes('getsupportcountrynew')) { | |
// https://gist.github.com/jasperck/130651ca255eec1772074e5d5428c363 | |
const XHR = unsafeWindow.XMLHttpRequest; | |
function newXHR() { | |
const xhr = new XHR(); | |
xhr.addEventListener('readystatechange', () => { | |
if (xhr.readyState === 4 && xhr.responseURL.includes('mtop.aliexpress.address.sdk.getsupportcountrynew')) { | |
const originalResponse = xhr.responseText; | |
Object.defineProperty(xhr, 'responseText', { writable: true }); | |
xhr.responseText = originalResponse.replace(/"sdkCountryList":\[.*?\]/, '"sdkCountryList":[]'); | |
} | |
}); | |
return xhr; | |
} | |
newXHR.prototype = XHR.prototype; | |
unsafeWindow.XMLHttpRequest = newXHR; | |
} | |
}, 1000); | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name AliExpress Address Randomizer Templates | |
// @description Шаблоны для AliExpress Address Randomizer | |
// @author longnull | |
// @namespace longnull | |
// @version 1.0.2 | |
// @match *://ilogisticsaddress.aliexpress.com/addressList.htm* | |
// @match *://ilogisticsaddress.aliexpress.ru/addressList.htm* | |
// @match *://shoppingcart.aliexpress.com/orders.htm* | |
// @match *://shoppingcart.aliexpress.ru/orders.htm* | |
// @match *://shoppingcart.aliexpress.com/order/confirm_order.htm* | |
// @match *://shoppingcart.aliexpress.ru/order/confirm_order.htm* | |
// @match *://www.aliexpress.com/p/trade/confirm.html* | |
// @match *://aliexpress.ru/checkout* | |
// @match *://aliexpress.ru/address-list* | |
// @grant GM_xmlhttpRequest | |
// @connect * | |
// @noframes | |
// ==/UserScript== | |
(() => { | |
'use strict'; | |
unsafeWindow.aarTemplates = { | |
// Шаблоны | |
}; | |
const httpRequest = (params) => { | |
return new Promise((resolve) => { | |
params.timeout = 30000; | |
params.onload = resolve; | |
params.onerror = resolve; | |
params.ontimeout = resolve; | |
params.onabort = resolve; | |
if (!params.method) { | |
params.method = 'GET'; | |
} | |
const func = typeof GM.xmlHttpRequest !== 'undefined' ? GM.xmlHttpRequest : GM_xmlhttpRequest; | |
func(params); | |
}); | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment