662 lines
No EOL
24 KiB
JavaScript
662 lines
No EOL
24 KiB
JavaScript
(async 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 findTextQuiz() {
|
||
const quizzes = document.querySelectorAll('section.quiz_type_text');
|
||
for (const quiz of quizzes) {
|
||
if (handledQuizzes.has(quiz)) continue;
|
||
if (quiz.classList.contains('quiz_answered')) { handledQuizzes.add(quiz); continue; }
|
||
const submitBtn = quiz.querySelector('button.quiz__submit');
|
||
if (!submitBtn) { handledQuizzes.add(quiz); continue; }
|
||
if (quiz.offsetParent !== null) return quiz;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function handleTextQuiz(quiz) {
|
||
console.log('✏️ Текстовый квиз...');
|
||
const textarea = quiz.querySelector('textarea.quiz__input-control');
|
||
if (textarea) {
|
||
simulateTyping(textarea, 'не знаю');
|
||
console.log('✓ Введён ответ');
|
||
await sleep(400);
|
||
}
|
||
|
||
const submitBtn = quiz.querySelector('button.quiz__submit');
|
||
if (submitBtn && !submitBtn.disabled) {
|
||
submitBtn.click();
|
||
console.log('✓ Нажата кнопка "Узнать ответ"');
|
||
} else if (submitBtn) {
|
||
submitBtn.disabled = false;
|
||
submitBtn.click();
|
||
console.log('✓ Принудительный клик "Узнать ответ"');
|
||
}
|
||
|
||
handledQuizzes.add(quiz);
|
||
await sleep(CLICK_DELAY);
|
||
return true;
|
||
}
|
||
|
||
function findCodingQuiz() {
|
||
const quizzes = document.querySelectorAll('section.quiz_type_coding');
|
||
for (const quiz of quizzes) {
|
||
if (handledQuizzes.has(quiz)) continue;
|
||
if (quiz.classList.contains('quiz_answered')) { handledQuizzes.add(quiz); continue; }
|
||
const runBtn = quiz.querySelector('button.quiz__coding-footer-action-button');
|
||
if (!runBtn) { handledQuizzes.add(quiz); continue; }
|
||
if (quiz.offsetParent !== null) return quiz;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function handleCodingQuiz(quiz) {
|
||
console.log('💻 Квиз с кодом — запускаем...');
|
||
const runBtn = quiz.querySelector('button.quiz__coding-footer-action-button');
|
||
runBtn.click();
|
||
|
||
// Ждём пока квиз пометится как решённый
|
||
const startTime = Date.now();
|
||
while (Date.now() - startTime < 60000) {
|
||
await sleep(CHECK_INTERVAL);
|
||
if (quiz.classList.contains('quiz_answered') || !quiz.querySelector('button.quiz__coding-footer-action-button')) {
|
||
console.log('✓ Код выполнен');
|
||
break;
|
||
}
|
||
}
|
||
handledQuizzes.add(quiz);
|
||
await sleep(CLICK_DELAY);
|
||
return true;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
function isLastFeedbackPage(form) {
|
||
const counter = form.querySelector('.quiz__counter');
|
||
if (!counter) return true;
|
||
const parts = counter.textContent.split('—').map(s => s.trim());
|
||
return parts.length === 2 && parts[0] === parts[1];
|
||
}
|
||
|
||
async function handleFeedbackPage(form) {
|
||
const textarea = form.querySelector(FEEDBACK_TEXTAREA_SELECTOR);
|
||
if (textarea) {
|
||
simulateTyping(textarea, 'СПАСИБО!');
|
||
console.log('✓ Введён текст "СПАСИБО!"');
|
||
await sleep(500);
|
||
|
||
let nextBtn = form.querySelector(FEEDBACK_NEXT_SELECTOR);
|
||
if (nextBtn && nextBtn.disabled) {
|
||
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 {
|
||
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) {
|
||
console.log('⚠️ Кнопка disabled, пробуем принудительный клик...');
|
||
nextBtn.disabled = false;
|
||
nextBtn.classList.remove('button2_disabled');
|
||
}
|
||
|
||
if (nextBtn) {
|
||
nextBtn.click();
|
||
console.log('✓ Нажата кнопка "Далее"');
|
||
await sleep(CLICK_DELAY);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
async function handleFeedbackForm(form) {
|
||
console.log('📝 Форма обратной связи...');
|
||
|
||
while (true) {
|
||
const last = isLastFeedbackPage(form);
|
||
const counter = form.querySelector('.quiz__counter');
|
||
console.log(`📄 Страница: ${counter ? counter.textContent.trim() : '?'}`);
|
||
|
||
await handleFeedbackPage(form);
|
||
|
||
if (last) break;
|
||
|
||
// Ждём загрузки следующей страницы формы
|
||
await sleep(CLICK_DELAY);
|
||
}
|
||
|
||
handledFeedbacks.add(form);
|
||
return true;
|
||
}
|
||
|
||
async function handleQuizForm(form) {
|
||
console.log('❓ Квиз...');
|
||
|
||
const checkboxes = form.querySelectorAll('input[type="checkbox"]');
|
||
if (checkboxes.length > 0) {
|
||
for (const cb of checkboxes) {
|
||
if (!cb.checked) cb.click();
|
||
await sleep(100);
|
||
}
|
||
console.log(`✓ Выбраны все чекбоксы (${checkboxes.length})`);
|
||
} else {
|
||
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 codingQuiz = findCodingQuiz();
|
||
if (codingQuiz) return { type: 'coding', element: codingQuiz };
|
||
|
||
const textQuiz = findTextQuiz();
|
||
if (textQuiz) return { type: 'text', element: textQuiz };
|
||
|
||
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 codingQuiz = findCodingQuiz();
|
||
if (codingQuiz) {
|
||
scrollToElement(codingQuiz);
|
||
await sleep(300);
|
||
await handleCodingQuiz(codingQuiz);
|
||
continue;
|
||
}
|
||
|
||
let textQuiz = findTextQuiz();
|
||
if (textQuiz) {
|
||
scrollToElement(textQuiz);
|
||
await sleep(300);
|
||
await handleTextQuiz(textQuiz);
|
||
continue;
|
||
}
|
||
|
||
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 === 'coding') {
|
||
scrollToElement(result.element);
|
||
await sleep(300);
|
||
await handleCodingQuiz(result.element);
|
||
continue;
|
||
}
|
||
|
||
if (result.type === 'text') {
|
||
scrollToElement(result.element);
|
||
await sleep(300);
|
||
await handleTextQuiz(result.element);
|
||
continue;
|
||
}
|
||
|
||
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);
|
||
|
||
// Проверяем не завис ли загрузчик
|
||
const loadingStart = Date.now();
|
||
while (document.querySelector('.page__loading')) {
|
||
await sleep(1000);
|
||
const elapsed = Date.now() - loadingStart;
|
||
if (elapsed > 60000) {
|
||
isRunning = false;
|
||
alert('⚠️ Страница грузится больше минуты. Обновите страницу и запустите скрипт снова.');
|
||
return;
|
||
}
|
||
}
|
||
clickedButtons = new WeakSet();
|
||
handledQuizzes = new WeakSet();
|
||
handledFeedbacks = new WeakSet();
|
||
}
|
||
|
||
console.log(`\n✅ Готово!`);
|
||
console.log(`📊 Уроков пройдено: ${lessonCount}`);
|
||
console.log(`🖱️ Кнопок нажато: ${clickCount}`);
|
||
}
|
||
|
||
function handleAssociatedProgramsPage() {
|
||
const cards = document.querySelectorAll('button.card.course-card');
|
||
for (const card of cards) {
|
||
const isCompleted = card.querySelector('.course-card__info-item_active');
|
||
if (!isCompleted) {
|
||
const title = card.querySelector('.course-card__title')?.textContent?.trim() || '(без названия)';
|
||
console.log(`🎯 Найдена непройденная программа: "${title}"`);
|
||
card.click();
|
||
return true;
|
||
}
|
||
}
|
||
console.log('✅ Все программы пройдены');
|
||
return false;
|
||
}
|
||
|
||
async function waitForModal() {
|
||
return new Promise(resolve => {
|
||
const startTime = Date.now();
|
||
const interval = setInterval(() => {
|
||
const el = document.querySelector('#portals .skills-modal_visible');
|
||
if (el) { clearInterval(interval); resolve(el); }
|
||
else if (Date.now() - startTime > MAX_WAIT_TIME) { clearInterval(interval); resolve(null); }
|
||
}, CHECK_INTERVAL);
|
||
});
|
||
}
|
||
|
||
async function handleCourseModal() {
|
||
console.log('🔍 Ищем непройденную тему в модалке...');
|
||
const modal = await waitForModal();
|
||
if (!modal) { console.log('⚠️ Модальное окно не появилось'); return false; }
|
||
|
||
// Шаг 1.js: найти и кликнуть первую тему без course-navigation-topic_solved
|
||
const topics = modal.querySelectorAll('a.course-navigation-topic');
|
||
let clickedTopic = false;
|
||
for (const topic of topics) {
|
||
if (!topic.classList.contains('course-navigation-topic_solved')) {
|
||
const title = topic.querySelector('.course-navigation-item__title')?.textContent?.trim() || '(без названия)';
|
||
console.log(`📂 Тема: "${title}"`);
|
||
topic.click();
|
||
clickedTopic = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!clickedTopic) { console.log('✅ Все темы пройдены'); return false; }
|
||
|
||
// Шаг 2: ждём пока появятся уроки внутри темы
|
||
await sleep(CLICK_DELAY);
|
||
const startTime = Date.now();
|
||
let lesson = null;
|
||
while (Date.now() - startTime < MAX_WAIT_TIME) {
|
||
const lessons = modal.querySelectorAll('a.course-navigation-lesson');
|
||
for (const l of lessons) {
|
||
if (!l.classList.contains('course-navigation-lesson_status_solved')) {
|
||
lesson = l;
|
||
break;
|
||
}
|
||
}
|
||
if (lesson) break;
|
||
await sleep(CHECK_INTERVAL);
|
||
}
|
||
|
||
if (!lesson) { console.log('⚠️ Непройденный урок не найден'); return false; }
|
||
|
||
const lessonTitle = lesson.querySelector('.course-navigation-item__title')?.textContent?.trim() || '(без названия)';
|
||
console.log(`📚 Урок: "${lessonTitle}"`);
|
||
lesson.click();
|
||
return true;
|
||
}
|
||
|
||
function insertSolutionIntoMonaco(code) {
|
||
const editors = window.monaco.editor.getEditors();
|
||
if (!editors || editors.length === 0) {
|
||
console.log('⚠️ Monaco редактор не найден');
|
||
return false;
|
||
}
|
||
const editor = editors[0];
|
||
const model = editor.getModel();
|
||
if (!model) {
|
||
console.log('⚠️ Monaco модель не найдена');
|
||
return false;
|
||
}
|
||
const fullRange = model.getFullModelRange();
|
||
editor.executeEdits('auto-solution', [{
|
||
range: fullRange,
|
||
text: code,
|
||
forceMoveMarkers: true,
|
||
}]);
|
||
editor.focus();
|
||
console.log('✅ Решение вставлено в редактор');
|
||
return true;
|
||
}
|
||
|
||
async function fetchAnswerPanes() {
|
||
const html = await fetch(window.location.href).then(r => r.text());
|
||
const match = html.match(/"answer_panes":(\[.*?\]),"has_author_solution"/);
|
||
if (!match) return null;
|
||
try {
|
||
return JSON.parse(match[1]);
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function handleTrainerPage() {
|
||
if (!document.querySelector('button.trainer-footer__solution-button')) {
|
||
console.log('💡 Загружаем исходник страницы для поиска решения...');
|
||
const answerPanes = await fetchAnswerPanes();
|
||
|
||
if (!answerPanes || answerPanes.length === 0) {
|
||
console.log('ℹ️ Авторского решения нет для этой задачи');
|
||
return false;
|
||
}
|
||
|
||
const solution = answerPanes[0].content;
|
||
if (!solution) {
|
||
console.log('ℹ️ Контент решения пустой');
|
||
return false;
|
||
}
|
||
|
||
console.log(`💡 Найдено решение (${answerPanes[0].name}), вставляем...`);
|
||
insertSolutionIntoMonaco(solution);
|
||
await sleep(CLICK_DELAY);
|
||
|
||
const checkBtn = document.querySelector('button.trainer-footer__check-button');
|
||
if (checkBtn) {
|
||
checkBtn.click();
|
||
console.log('✅ Нажата кнопка "Проверить", ждём результата...');
|
||
} else {
|
||
console.log('⚠️ Кнопка "Проверить" не найдена');
|
||
return true;
|
||
}
|
||
} else {
|
||
console.log('ℹ️ Задача уже решена');
|
||
}
|
||
|
||
// Ждём кнопку "Далее" без ограничения по времени
|
||
let nextBtn = null;
|
||
let elapsed = 0;
|
||
while (!nextBtn) {
|
||
await sleep(CHECK_INTERVAL);
|
||
elapsed += CHECK_INTERVAL;
|
||
nextBtn = document.querySelector('button.trainer-footer__solution-button');
|
||
if (elapsed % 10000 === 0) {
|
||
console.log(`⏳ Ожидаем результат проверки... (${elapsed / 1000}с)`);
|
||
}
|
||
}
|
||
nextBtn.click();
|
||
console.log('➡️ Нажата кнопка "Далее"');
|
||
return true;
|
||
}
|
||
|
||
async function dispatch() {
|
||
// Сброс состояния при каждом вызове
|
||
isRunning = true;
|
||
clickedButtons = new WeakSet();
|
||
handledQuizzes = new WeakSet();
|
||
handledFeedbacks = new WeakSet();
|
||
|
||
const path = window.location.pathname;
|
||
console.log(`🔄 Страница: ${path}`);
|
||
|
||
if (path.includes('/profile/associated-programs-android-st-v2')) {
|
||
const found = handleAssociatedProgramsPage();
|
||
if (found) await handleCourseModal();
|
||
} else if (path.includes('/trainer/') && window.monaco?.editor.getEditors().length > 0) {
|
||
await handleTrainerPage();
|
||
} else if (path.includes('/trainer/') || path.includes('/topics/')) {
|
||
await run();
|
||
}
|
||
}
|
||
|
||
// Перехват SPA-навигации
|
||
const _pushState = history.pushState.bind(history);
|
||
history.pushState = function(...args) {
|
||
_pushState(...args);
|
||
setTimeout(() => dispatch(), PAGE_LOAD_DELAY);
|
||
};
|
||
window.addEventListener('popstate', () => {
|
||
setTimeout(() => dispatch(), PAGE_LOAD_DELAY);
|
||
});
|
||
|
||
window.stopAutoClick = function() {
|
||
isRunning = false;
|
||
console.log('⏹ Остановлено');
|
||
};
|
||
|
||
console.log('💡 Для остановки: stopAutoClick()');
|
||
await dispatch();
|
||
})(); |