SubsPlease Time Left Until Release

Time left until release of shows today and tomorrow

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         SubsPlease Time Left Until Release
// @version      1.0.7
// @description  Time left until release of shows today and tomorrow
// @author       Hoshiburst
// @match        *://subsplease.org/
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js
// @license      GPL-2.0+; http://www.gnu.org/licenses/gpl-2.0.txt
// @namespace    https://greasyfork.org/en/users/91364-hoshiburst
// @run-at       document-idle
// @noframes
// ==/UserScript==

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

/**
 * Sleep until the schedule table is loaded before we replace its contents.
 * Looking at the site's script, `load_schedule` will append a timer once the data is loaded so we can wait for that
 * to exist (instead of waiting until the schedule table has items - since it may be empty if a day has no shows)
 */
const sleepUntilTableLoads = async () => {
  let wcTimeElement = document.querySelector("#current-time .wc_time");
  while(!wcTimeElement) {
    await sleep(1000);
    wcTimeElement = document.querySelector("#current-time .wc_time");
  }
};

/**
 * Fetch the full schedule for a given timezone
 */
const getFullSchedule = async (timeZoneName) => {
  const response = await fetch(`https://subsplease.org/api/?f=schedule&tz=${timeZoneName}`);
  if (response.status !== 200) throw new Error(`Error fetching schedule: response status ${response.status}`);
  return (await response.json()).schedule;
}

const weekday = [
  'Sunday',
  'Monday',
  'Tuesday',
  'Wednesday',
  'Thursday',
  'Friday',
  'Saturday',
];

const zeroPad = (num) => num < 10 ? `0${num}` : `${num}`;

/**
 * Format milliseconds as +/-HH:mm
 */
const formatTimeMs = (ms) => {
  const duration = moment.duration(ms);
  const hoursFloat = Math.abs(duration.asHours());
  const hours = Math.trunc(hoursFloat);
  const minutes = Math.trunc((hoursFloat - hours) * 60);
  const sign = ms >= 0 ? '+' : '-';
  const timeLeft = `${sign}${zeroPad(hours)}:${zeroPad(minutes)}`;
  return timeLeft;
}

const addTimeLeftUntilShowtime = (show, showDate, now) => {
  const showTime = moment(`${showDate} ${show.time}`);
  const timeLeftMs = moment(showTime).diff(now);
  return {
    ...show,
    day: showTime.format('ddd'),
    aired: timeLeftMs < 0,
    timeLeft: formatTimeMs(timeLeftMs),
  }
};

/**
 * Build a html row for each schedule entry
 */
const buildRow = (show) => {
  const tr = document.createElement('tr');
  tr.className = 'schedule-widget-item';

  const showTd = document.createElement('td');
  const timeTd = document.createElement('td');
  showTd.className = 'schedule-widget-show';
  timeTd.className = 'schedule-widget-time';
  tr.appendChild(showTd);
  tr.appendChild(timeTd);

  const showLink = document.createElement('a');
  showLink.title = "Go to show";
  showLink.href = `/shows/${show.page}`;
  showLink.text = show.title;
  showLink.setAttribute('data-preview-image', show.image_url)
  showTd.appendChild(showLink);

  if (show.aired) {
    const timeImg = document.createElement('img');
    timeTd.appendChild(timeImg);
    timeImg.setAttribute('draggable', 'false');
    timeImg.setAttribute('role', 'img');
    timeImg.setAttribute('alt', '✔');
    timeImg.src = 'https://s.w.org/images/core/emoji/13.0.0/svg/2714.svg';
    timeImg.className = 'emoji';
  }
  const padding = show.aired ? ' ' : '';

  const timeTdText = document.createTextNode(`${padding}${show.day} ${show.time} (${show.timeLeft})`);
  timeTd.appendChild(timeTdText);

  return tr;
}

/**
 * Replace the old schedule table
 */
const populateScheduleTable = async (schedule) => {
  await sleepUntilTableLoads();
  const table = document.getElementById('schedule-table');
  table.innerHTML = "";
  const todayRows = schedule.today.map(buildRow);
  const tomorrowRows = schedule.tomorrow.map(buildRow);
  todayRows.forEach(row => table.appendChild(row));
  tomorrowRows.forEach(row => table.appendChild(row));
}

const run = async () => {
  const timeZoneName = Intl.DateTimeFormat().resolvedOptions().timeZone;
  const fullSchedule = await getFullSchedule(timeZoneName);
  const now = moment();
  const todayDate = now.format('YYYY-MM-DD');
  const tomorrowDate = now.clone().add(1, 'day').format('YYYY-MM-DD');
  const todayAndTomorrowSchedule = {
    today: fullSchedule[weekday[now.day()]].map(show => addTimeLeftUntilShowtime(show, todayDate, now)),
    tomorrow: fullSchedule[weekday[(now.day() + 1) % 7]].map(show => addTimeLeftUntilShowtime(show, tomorrowDate, now))
  }
  await populateScheduleTable(todayAndTomorrowSchedule);
}

run();