LRN

Makes LRN training less painful

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name        LRN
// @namespace   urn://https://www.georgegillams.co.uk/api/greasemonkey/lrn
// @include     *lrn.com*
// @exclude     none
// @version     0.0.11
// @description:en	Makes LRN training less painful
// @grant    		none
// @description Makes LRN training less painful
// @license MIT
// ==/UserScript==

/**
 * USAGE INSTRUCTIONS:
 *
 * BEFORE YOU USE THIS: Start the course and do the intro manually. This section determines which sections you have to go through and you don't want the script randomly choosing loads of them!
 *
 * Either: Install using the TamperMonkey browser extension
 * Or: Copy the popup URL to open the course in a normal tab, open dev tools (using menu as they've disabled right-click) and paste the script into the console.
 *
 * Sometimes it gets stuck, so if it does just reload (and if using the copy-paste method, re-execute in the console).
 *
 * Finally, if you've completed everything but it still needs you to spend more time (it requires you to spend at least 90 mins) there's a new "Slow mode" - toggle this on at the top of the script and re-open any completed section to go through it super slowly!
 */

const DEBUG = false;
const ATTEMPT_QUIZZES = true;
const SLOW_MODE = false;

let isWorking = false;

const getRandomItem = (arr) => {
  const randomIndex = Math.floor(Math.random() * arr.length);
  return arr[randomIndex] ?? arr[0];
};

const pause = (duration) =>
  new Promise((res) => setTimeout(() => res(), duration));

const debug = DEBUG ? (...args) => console.log('DEBUG:', ...args) : () => null;

const closePopup = () => {
  const closeButton = document.getElementById('POP_CLOSE');
  if (!closeButton) {
    return;
  }
  closeButton.click();
};

const isAnswerCheckbox = (element) =>
  element.getAttribute('type') === 'checkbox' &&
  element.id.startsWith('SAQ_CHECKBOX');
const isAnswerRadio = (element) =>
  element.getAttribute('type') === 'radio' &&
  element.id.startsWith('SAQ_RADIO');

const speedUpVideo = () => {
  const videos = [...document.getElementsByTagName('video')];
  debug('Speeding up videos', videos);
  videos.forEach((video) => {
    video.playbackRate = 4.0;
    if (video?.paused) {
      video.play();
    }
  });
};

const openHomepageSectionIfAvailable = async () => {
  const homepageSectionCards =
    document.getElementsByClassName('ip-tile-card-inner');
  debug('Homepage sections', homepageSectionCards);
  if (!homepageSectionCards.length) {
    return;
  }

  const unlockedHomepageSectionCards = [...homepageSectionCards].filter(
    (section) => {
      return !section.querySelector('.ip-icon-lock');
    },
  );
  debug('unlockedHomepageSectionCards', unlockedHomepageSectionCards);
  if (!unlockedHomepageSectionCards.length) {
    return;
  }

  const incompleteHomepageSectionCards = [
    ...unlockedHomepageSectionCards,
  ].filter((section) => {
    return !section.querySelector('.ip-tile-check');
  });
  debug('incompleteHomepageSectionCards', incompleteHomepageSectionCards);
  if (
    incompleteHomepageSectionCards.some((section) =>
      section.innerText.includes('Introduction'),
    )
  ) {
    debug('Introduction section found, skipping');
    return;
  }
  if (!incompleteHomepageSectionCards.length) {
    return;
  }
  debug('Filtered homepage sections', incompleteHomepageSectionCards);
  await pause(2000);
  getRandomItem(incompleteHomepageSectionCards).click();
};

const selectThumnailButtonIfAvailable = () => {
  // get all elements by class ip-ctr-select-thumbnail-btn
  // for each, click it
  const thumbnailButtons = [
    ...document.getElementsByClassName('ip-ctr-select-thumbnail-btn'),
  ];
  debug('thumbnailButtons', thumbnailButtons);
  if (!thumbnailButtons.length) {
    return;
  }
  isWorking = true;
  for (let button of thumbnailButtons) {
    debug('Selecting thumbnail button', button);
    button.click();
  }
  isWorking = false;
};

const pressPlayVideo = () => {
  const playButton = document.getElementById('VIDEO_PLAY_BUTTON');
  debug('Play button', playButton);
  if (!playButton) {
    return;
  }
  playButton.click();
  debug('Pressing play button');
};

const selectRandomBinaryOption = () => {
  if (!ATTEMPT_QUIZZES) {
    return;
  }

  const radioInputs = [...document.getElementsByTagName('INPUT')].filter(
    (input) => input.id.startsWith('RADIO-'),
  );
  debug('radioInputs', radioInputs);
  if (!radioInputs.length) {
    return;
  }
  getRandomItem(radioInputs).click();
};

const answerQuestionIfAvailable = () => {
  const scorePieChart = document.getElementById('PIE_TEXT_QUICK_QUIZ_PROGRESS');
  if (scorePieChart && !ATTEMPT_QUIZZES) {
    // We can't automate this part as we need the answers to be correct!
    debug('Not attempting quiz');
    return;
  }

  const submitButton =
    document.getElementById('IP_SAQ_SUBMIT_BUTTON') ||
    document.getElementById('SUBMIT_BUTTON') ||
    document.getElementById('RETRY_BUTTON');
  debug('submitButton', submitButton);
  if (!submitButton) {
    return;
  }
  debug('Attempting question(s)');
  const submitDisabled = submitButton.getAttribute('aria-disabled') === 'true';
  debug('submitDisabled', submitDisabled);
  if (!submitDisabled) {
    submitButton.click();
  }

  // The submit button cannot yet be pressed, so we need to select more answers
  const uncheckedAnswers = [...document.getElementsByTagName('INPUT')].filter(
    (input) =>
      (isAnswerCheckbox(input) || isAnswerRadio(input)) &&
      input.getAttribute('aria-checked') !== 'true',
  );
  debug('uncheckedAnswers', uncheckedAnswers);
  if (!uncheckedAnswers.length) {
    return;
  }
  getRandomItem(uncheckedAnswers).click();
};

const expandSectionsIfAvailable = async () => {
  const expandableItems = [
    ...document.getElementsByClassName('ip-ctr-inner-btn'),
    ...document.getElementsByClassName('ip-accordion-popupBtn'),
    ...document.getElementsByClassName('ctrhotspot-hotspot'),
  ];
  debug('Expandable items', expandableItems);
  if (!expandableItems.length) {
    return;
  }
  isWorking = true;
  for (let item of expandableItems) {
    debug('Expandable item', item);
    item.click();
    await pause(800);
  }
  isWorking = false;
};

const pressNextIfAvailable = () => {
  const nextButton = document.getElementById('NAV_NEXT');
  debug('nextButton', nextButton);
  if (!nextButton) {
    return;
  }
  const isDisabled = nextButton.getAttribute('aria-disabled') === 'true';
  debug('nextButton isDisabled', isDisabled);
  if (isDisabled) {
    return;
  }
  // TODO: Hide video elements? Maybe they're interfering with the next button?
  nextButton.click();
};

const pressSwitchToTextIfAvailable = () => {
  const switchButton = document.getElementById('BTN_TEXT_MODE');
  debug('switchButton', switchButton);
  if (!switchButton) {
    return;
  }
  const isDisabled = switchButton.getAttribute('aria-disabled') === 'true';
  debug('switchButton isDisabled', isDisabled);
  if (isDisabled) {
    return;
  }
  switchButton.click();
};

function doActions() {
  openHomepageSectionIfAvailable();
  pressSwitchToTextIfAvailable();
  selectThumnailButtonIfAvailable();
  pressNextIfAvailable();
  expandSectionsIfAvailable();
  answerQuestionIfAvailable();
  selectRandomBinaryOption();
  pressPlayVideo();
  speedUpVideo();
  closePopup();
}

function worker() {
  try {
    // If another action is in progress, then don't trigger new actions yet...
    if (!isWorking) {
      doActions();
    }
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log(e);
  }
}

setInterval(worker, SLOW_MODE ? 15000 : 500);