您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Time left until release of shows today and tomorrow
// ==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();