init
This commit is contained in:
commit
cc3e2c1b10
1 changed files with 345 additions and 0 deletions
345
script.js
Normal file
345
script.js
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
(function autoClickSequential() {
|
||||||
|
// Настройки
|
||||||
|
const CLICK_DELAY = 800;
|
||||||
|
const CHECK_INTERVAL = 500;
|
||||||
|
const MAX_WAIT_TIME = 5000;
|
||||||
|
const PAGE_LOAD_DELAY = 2000;
|
||||||
|
|
||||||
|
// Селекторы
|
||||||
|
const BUTTON_SELECTOR = 'button.content-expander__button';
|
||||||
|
const NEXT_LESSON_SELECTOR = 'button[data-test-id="next-lesson-control-button"]';
|
||||||
|
const FEEDBACK_FORM_SELECTOR = 'section.quiz_type_feedback';
|
||||||
|
const FEEDBACK_NEXT_SELECTOR = 'button.quiz__move-on';
|
||||||
|
const FEEDBACK_TEXTAREA_SELECTOR = 'textarea.input__control';
|
||||||
|
const QUIZ_FORM_SELECTOR = 'form.quiz_type_select';
|
||||||
|
const QUIZ_SUBMIT_SELECTOR = 'button.quiz__submit';
|
||||||
|
|
||||||
|
let clickCount = 0;
|
||||||
|
let lessonCount = 0;
|
||||||
|
let isRunning = true;
|
||||||
|
let clickedButtons = new WeakSet();
|
||||||
|
let handledQuizzes = new WeakSet();
|
||||||
|
let handledFeedbacks = new WeakSet();
|
||||||
|
|
||||||
|
function findNewButton() {
|
||||||
|
const buttons = document.querySelectorAll(BUTTON_SELECTOR);
|
||||||
|
for (const button of buttons) {
|
||||||
|
if (clickedButtons.has(button)) continue;
|
||||||
|
if (button.offsetParent !== null) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNextLessonButton() {
|
||||||
|
const button = document.querySelector(NEXT_LESSON_SELECTOR);
|
||||||
|
if (button && button.offsetParent !== null) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFeedbackForm() {
|
||||||
|
const forms = document.querySelectorAll(FEEDBACK_FORM_SELECTOR);
|
||||||
|
for (const form of forms) {
|
||||||
|
if (handledFeedbacks.has(form)) continue;
|
||||||
|
if (form.offsetParent !== null) {
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isQuizAnswered(form) {
|
||||||
|
const checkedRadio = form.querySelector('input[type="radio"][aria-checked="true"]');
|
||||||
|
if (checkedRadio) return true;
|
||||||
|
if (form.classList.contains('quiz_answered')) return true;
|
||||||
|
const submitBtn = form.querySelector(QUIZ_SUBMIT_SELECTOR);
|
||||||
|
if (!submitBtn) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findQuizForm() {
|
||||||
|
const forms = document.querySelectorAll(QUIZ_FORM_SELECTOR);
|
||||||
|
for (const form of forms) {
|
||||||
|
if (handledQuizzes.has(form)) continue;
|
||||||
|
if (isQuizAnswered(form)) {
|
||||||
|
handledQuizzes.add(form);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (form.offsetParent !== null) {
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Симуляция нативного ввода текста
|
||||||
|
function simulateTyping(textarea, text) {
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
// Устанавливаем значение через нативный setter
|
||||||
|
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||||
|
window.HTMLTextAreaElement.prototype, 'value'
|
||||||
|
).set;
|
||||||
|
nativeInputValueSetter.call(textarea, text);
|
||||||
|
|
||||||
|
// Триггерим все возможные события
|
||||||
|
textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
|
||||||
|
textarea.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
|
||||||
|
textarea.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true }));
|
||||||
|
textarea.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));
|
||||||
|
textarea.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true }));
|
||||||
|
|
||||||
|
// React-specific события
|
||||||
|
const reactEvent = new Event('input', { bubbles: true });
|
||||||
|
reactEvent.simulated = true;
|
||||||
|
textarea.dispatchEvent(reactEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFeedbackForm(form) {
|
||||||
|
console.log('📝 Форма обратной связи...');
|
||||||
|
|
||||||
|
// Проверяем, есть ли текстовое поле
|
||||||
|
const textarea = form.querySelector(FEEDBACK_TEXTAREA_SELECTOR);
|
||||||
|
if (textarea) {
|
||||||
|
// Симулируем ввод текста
|
||||||
|
simulateTyping(textarea, 'СПАСИБО!');
|
||||||
|
console.log('✓ Введён текст "СПАСИБО!"');
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
// Пробуем ещё раз если кнопка всё ещё disabled
|
||||||
|
let nextBtn = form.querySelector(FEEDBACK_NEXT_SELECTOR);
|
||||||
|
if (nextBtn && nextBtn.disabled) {
|
||||||
|
// Ещё один способ - через React fiber
|
||||||
|
const reactKey = Object.keys(textarea).find(key => key.startsWith('__reactFiber') || key.startsWith('__reactProps'));
|
||||||
|
if (reactKey) {
|
||||||
|
const reactProps = textarea[reactKey];
|
||||||
|
if (reactProps && reactProps.onChange) {
|
||||||
|
reactProps.onChange({ target: textarea });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await sleep(300);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Обрабатываем radio-кнопки
|
||||||
|
const radioGroups = form.querySelectorAll('.likert-scale__statement-row');
|
||||||
|
for (const row of radioGroups) {
|
||||||
|
const firstRadio = row.querySelector('input[type="radio"]');
|
||||||
|
if (firstRadio && !firstRadio.checked) {
|
||||||
|
firstRadio.click();
|
||||||
|
await sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('✓ Выбраны ответы "да"');
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(300);
|
||||||
|
|
||||||
|
// Ждём пока кнопка станет активной
|
||||||
|
let nextBtn = form.querySelector(FEEDBACK_NEXT_SELECTOR);
|
||||||
|
let attempts = 0;
|
||||||
|
while (nextBtn && nextBtn.disabled && attempts < 15) {
|
||||||
|
await sleep(200);
|
||||||
|
nextBtn = form.querySelector(FEEDBACK_NEXT_SELECTOR);
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextBtn && !nextBtn.disabled) {
|
||||||
|
nextBtn.click();
|
||||||
|
console.log('✓ Нажата кнопка "Далее"');
|
||||||
|
handledFeedbacks.add(form);
|
||||||
|
await sleep(CLICK_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если кнопка всё ещё disabled, пробуем кликнуть принудительно
|
||||||
|
if (nextBtn) {
|
||||||
|
console.log('⚠️ Кнопка disabled, пробуем принудительный клик...');
|
||||||
|
nextBtn.disabled = false;
|
||||||
|
nextBtn.classList.remove('button2_disabled');
|
||||||
|
nextBtn.click();
|
||||||
|
handledFeedbacks.add(form);
|
||||||
|
await sleep(CLICK_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handledFeedbacks.add(form);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleQuizForm(form) {
|
||||||
|
console.log('❓ Квиз...');
|
||||||
|
|
||||||
|
const firstRadio = form.querySelector('input[type="radio"]');
|
||||||
|
if (firstRadio) {
|
||||||
|
firstRadio.click();
|
||||||
|
console.log('✓ Выбран первый вариант');
|
||||||
|
await sleep(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitBtn = form.querySelector(QUIZ_SUBMIT_SELECTOR);
|
||||||
|
if (submitBtn && !submitBtn.disabled) {
|
||||||
|
submitBtn.click();
|
||||||
|
console.log('✓ Нажата кнопка "Узнать ответ"');
|
||||||
|
handledQuizzes.add(form);
|
||||||
|
await sleep(CLICK_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(200);
|
||||||
|
const submitBtnRetry = form.querySelector(QUIZ_SUBMIT_SELECTOR);
|
||||||
|
if (submitBtnRetry && !submitBtnRetry.disabled) {
|
||||||
|
submitBtnRetry.click();
|
||||||
|
console.log('✓ Нажата кнопка "Узнать ответ"');
|
||||||
|
handledQuizzes.add(form);
|
||||||
|
await sleep(CLICK_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handledQuizzes.add(form);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToElement(element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForElement(timeout = MAX_WAIT_TIME) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
if (!isRunning) return null;
|
||||||
|
|
||||||
|
const quiz = findQuizForm();
|
||||||
|
if (quiz) return { type: 'quiz', element: quiz };
|
||||||
|
|
||||||
|
const feedback = findFeedbackForm();
|
||||||
|
if (feedback) return { type: 'feedback', element: feedback };
|
||||||
|
|
||||||
|
const nextLesson = findNextLessonButton();
|
||||||
|
if (nextLesson) return { type: 'next', button: nextLesson };
|
||||||
|
|
||||||
|
const button = findNewButton();
|
||||||
|
if (button) return { type: 'expander', button: button };
|
||||||
|
|
||||||
|
await sleep(CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processLesson() {
|
||||||
|
while (isRunning) {
|
||||||
|
let quiz = findQuizForm();
|
||||||
|
if (quiz) {
|
||||||
|
scrollToElement(quiz);
|
||||||
|
await sleep(300);
|
||||||
|
await handleQuizForm(quiz);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let feedback = findFeedbackForm();
|
||||||
|
if (feedback) {
|
||||||
|
scrollToElement(feedback);
|
||||||
|
await sleep(300);
|
||||||
|
await handleFeedbackForm(feedback);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextLesson = findNextLessonButton();
|
||||||
|
if (nextLesson) {
|
||||||
|
return nextLesson;
|
||||||
|
}
|
||||||
|
|
||||||
|
let button = findNewButton();
|
||||||
|
|
||||||
|
if (!button) {
|
||||||
|
console.log('⏳ Ожидание...');
|
||||||
|
const result = await waitForElement();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
console.log('🏁 Элементов больше нет');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'quiz') {
|
||||||
|
scrollToElement(result.element);
|
||||||
|
await sleep(300);
|
||||||
|
await handleQuizForm(result.element);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'feedback') {
|
||||||
|
scrollToElement(result.element);
|
||||||
|
await sleep(300);
|
||||||
|
await handleFeedbackForm(result.element);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.type === 'next') {
|
||||||
|
return result.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
button = result.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToElement(button);
|
||||||
|
await sleep(300);
|
||||||
|
|
||||||
|
clickedButtons.add(button);
|
||||||
|
|
||||||
|
const buttonText = button.textContent.trim();
|
||||||
|
button.click();
|
||||||
|
clickCount++;
|
||||||
|
console.log(`✓ [${clickCount}] Нажата: "${buttonText}"`);
|
||||||
|
|
||||||
|
await sleep(CLICK_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
console.log('🚀 Запуск...');
|
||||||
|
|
||||||
|
while (isRunning) {
|
||||||
|
lessonCount++;
|
||||||
|
console.log(`📖 Урок ${lessonCount}`);
|
||||||
|
|
||||||
|
const nextLessonBtn = await processLesson();
|
||||||
|
|
||||||
|
if (!nextLessonBtn) {
|
||||||
|
console.log('🏁 Уроки закончились');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToElement(nextLessonBtn);
|
||||||
|
await sleep(300);
|
||||||
|
nextLessonBtn.click();
|
||||||
|
console.log('➡️ Переход к следующему уроку...');
|
||||||
|
|
||||||
|
await sleep(PAGE_LOAD_DELAY);
|
||||||
|
clickedButtons = new WeakSet();
|
||||||
|
handledQuizzes = new WeakSet();
|
||||||
|
handledFeedbacks = new WeakSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n✅ Готово!`);
|
||||||
|
console.log(`📊 Уроков пройдено: ${lessonCount}`);
|
||||||
|
console.log(`🖱️ Кнопок нажато: ${clickCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.stopAutoClick = function() {
|
||||||
|
isRunning = false;
|
||||||
|
console.log('⏹ Остановлено');
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('💡 Для остановки: stopAutoClick()');
|
||||||
|
run();
|
||||||
|
})();
|
||||||
Loading…
Add table
Reference in a new issue