From 65d9a8e2820b4f51a9ae16f9ce1a9c4ec6d7f0a1 Mon Sep 17 00:00:00 2001 From: Thayol Date: Sat, 2 Dec 2023 00:48:33 +0100 Subject: [PATCH] this commit is a lie --- .gitignore | 6 ++ README.md | 33 ++++++++ data/championships-to-json.php | 65 ++++++++++++++++ data/championships.txt | 61 +++++++++++++++ index.php | 24 ++++++ script.js | 138 +++++++++++++++++++++++++++++++++ src/.keep | 0 style.css | 92 ++++++++++++++++++++++ 8 files changed, 419 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 data/championships-to-json.php create mode 100644 data/championships.txt create mode 100755 index.php create mode 100644 script.js create mode 100644 src/.keep create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..790fe14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Ignore copyrighted resources, get them at https://www.starstable.com/article/8934 +src/background.jpg +src/favicon-32x32.png + +# Ignore the processed data to make sure there aren't multiple sources of truth +data/championships.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba12e09 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Star Stable Timer + +## Setup + +Put your favicon to `src/favicon-32x32.png` and your page background to `src/background.jpg` +Perform the championship time update. + +## Updating Championship Times + +[The official site](https://www.starstable.com/game/championships) has the times published as images, so it isn't really possible to fetch them without OCR, and even then the publised times are quite out of date usually... + +Since most other sources are outdated too, you will have to hand-type your championship times. Save them into `data/championships.txt` in the following format: + +``` +The Something Championship + +Monday: 00:10, 02:00, 03:30 +Tuesday: 09:00, 10:00 + +Somewhere Else + +Tuesday: 01:30, 01:50, 01:59 +Wednesday: 02:00, 03:00, 04:00 +``` + +Then execute: + +```sh +cd data +php championships-to-json.php +``` + +Once the command completes, you have the latest schedule ready to use. diff --git a/data/championships-to-json.php b/data/championships-to-json.php new file mode 100644 index 0000000..d7dc219 --- /dev/null +++ b/data/championships-to-json.php @@ -0,0 +1,65 @@ + 1, + 'Tuesday' => 2, + 'Wednesday' => 3, + 'Thursday' => 4, + 'Friday' => 5, + 'Saturday' => 6, + 'Sunday' => 0, +]; + +$championships_by_days = []; + +foreach ($championships_by_time as $championship) { + [$day_raw, $time_raw, $location] = $championship; + + $day = $days[$day_raw]; + + $time_components = explode(':', $time_raw); + $time = 60*intval($time_components[0]) + intval($time_components[1]); + + if (!isset($championships_by_days[$day])) { + $championships_by_days[$day] = []; + } + + $championships_by_days[$day][$time] = $location; +} + +// optional, but it's compile time so sorting doesn't really hurt performance +foreach ($championships_by_days as $key => $_value) { ksort($championships_by_days[$key]); } + +$json = json_encode($championships_by_days, JSON_PRETTY_PRINT) . "\n"; +file_put_contents("championships.json", $json); diff --git a/data/championships.txt b/data/championships.txt new file mode 100644 index 0000000..91f3b51 --- /dev/null +++ b/data/championships.txt @@ -0,0 +1,61 @@ +This is just a sample, and it might be updated! + +The Firgrove Championship + +Monday: 03:30, 09:30, 15:30, 21:30 +Tuesday: 03:00, 09:00, 15:00, 21:00 +Wednesday: 02:30, 08:30, 14:30, 20:30 +Thursday: 02:00, 08:00, 12:00, 14:00, 20:00 +Friday: 01:00, 07:00, 13:00, 19:00 +Saturday: 00:00, 06:00, 10:00, 15:00, 19:00 +Sunday: 04:00, 09:00, 17:30, 23:00 + +The Fort Pinta Championship + +Monday: 00:30, 04:30, 08:30, 12:30, 16:30, 18:00, 20:30 +Tuesday: 00:00, 04:00, 08:00, 12:00, 16:00, 20:00 +Wednesday: 03:30, 07:30, 11:30, 15:30, 19:30, 23:30 +Thursday: 03:00, 07:00, 11:00, 15:00, 19:00, 23:00 +Friday: 02:00, 06:00, 10:00, 14:00, 18:00, 22:00 +Saturday: 01:30, 07:30, 12:00, 16:30, 21:00 +Sunday: 00:30, 05:00, 06:30, 11:00, 15:30, 20:00 + +The Jorvik Stables Championship + +Monday: 02:30, 07:00, 14:30, 22:30 +Tuesday: 02:00, 06:30, 14:00, 22:00 +Wednesday: 01:30, 06:00, 13:30, 21:30 +Thursday: 01:00, 05:30, 13:00, 21:00 +Friday: 00:00, 04:30, 12:00, 15:30, 20:00 +Saturday: 03:30, 06:30, 10:30, 17:30, 19:30 +Sunday: 02:30, 09:30, 14:00, 18:30 + +The Moorland Championship + +Monday: 00:00, 04:00, 08:00, 12:00, 16:00, 20:00 +Tuesday: 03:30, 07:30, 11:30, 15:30, 19:30, 23:30 +Wednesday: 03:00, 07:00, 11:00, 15:00, 17:00, 19:00, 23:00 +Thursday: 02:30, 06:30, 10:30, 14:30, 18:30, 22:30 +Friday: 01:30, 05:30, 09:30, 13:30, 17:30, 21:30 +Saturday: 01:00, 07:00, 11:30, 16:00, 20:30 +Sunday: 00:00, 04:30, 06:00, 10:30, 15:00, 19:30 + +The Silversong Pony Championship + +Monday: 01:00, 05:00, 09:00, 13:00, 17:00, 21:00 +Tuesday: 00:30, 04:30, 08:30, 12:30, 16:30, 20:30 +Wednesday: 00:00, 04:00, 08:00, 12:00, 16:00, 20:00 +Thursday: 03:30, 07:30, 11:30, 15:30, 19:30, 23:30 +Friday: 02:30, 06:30, 10:30, 14:30, 16:30, 18:30, 22:30 +Saturday: 02:00, 08:00, 12:30, 17:00, 21:30 +Sunday: 01:00, 05:30, 07:00, 11:30, 16:00, 20:30 + +The Silverglade Village Championship + +Monday: 03:00, 07:30, 13:30, 17:30, 19:30 +Tuesday: 02:30, 07:00, 17:00, 19:00 +Wednesday: 02:00, 06:30, 16:30, 18:30 +Thursday: 01:30, 06:00, 16:00, 18:00 +Friday: 00:30, 05:00, 15:00, 17:00 +Saturday: 04:00, 05:30, 14:00, 18:00, 23:00 +Sunday: 03:00, 13:00, 18:00, 22:00 diff --git a/index.php b/index.php new file mode 100755 index 0000000..1b2ec44 --- /dev/null +++ b/index.php @@ -0,0 +1,24 @@ + + + + SSO Timer + + + + +
+ +
+
+

Next Up

+

The ... Championship

+

at 00:00

+

Remaining: 00:00:00

+
+
+ +
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..8ffc2c7 --- /dev/null +++ b/script.js @@ -0,0 +1,138 @@ +var championships = {} +var lastChampionship = null +var locations = [] +var updateFrequency = 500; // in ms + +function flattenObjectIntoSingleUniqueArray(object) { + return [...new Set(Object.values(object).reduce((arr, t) => arr.concat(Object.values(t)), []))] +} + +function getDateObject() { + return new Date() // maybe easier to offset time zone later +} + +function currentDay() { + return getDateObject().getDay() +} + +function currentTime() { + let date = getDateObject() + return date.getHours() * 60 + date.getMinutes() +} + +function nextDay() { + let day = currentDay() + 1 + if (day > 6) day -= 6 + + return day +} + +function firstTimeOnDay(day) { + return Math.min(...Object.keys(championships[day])) +} + +function firstTimeNextDay() { + return firstTimeOnDay(nextDay()) +} + +function nextTime() { + let time = currentTime() + + let nextTimes = Object.keys(championships[currentDay()]).filter((t) => parseInt(t) >= time) + + if (nextTimes.length < 1) { + return null + } + + return Math.min(...nextTimes) +} + +function formatTime(time) { + return Math.floor(time/60).toString().padStart(2, '0') + ":" + (time % 60).toString().padStart(2, '0') +} + +function formatSeconds() { + return (60 - getDateObject().getSeconds()).toString().padStart(2, '0') +} + +function nextChampionship() { + let remainingOffset = 0 + let day = currentDay() + let time = nextTime() + + if (time == null) { + remainingOffset = 1440 + day = nextDay() + time = firstTimeNextDay() + } + + return { + day: day, + time: time, + location: championships[day][time], + remaining: (remainingOffset + time) - currentTime(), + } +} + +function updateChampionshipDetails(next) { + document.getElementById('time').innerHTML = formatTime(next.time) + document.getElementById('location').innerHTML = next.location +} + +function updateTimer(next) { + let remaining = formatTime(next.remaining) + ":" + formatSeconds() + document.getElementById('timer').innerHTML = remaining + document.title = `${remaining} - ${next.location} - SSO Timer` +} + +function updateChampionshipTable(next) { + const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] + let table = `` + for (const [day, details] of Object.entries(championships)) { + let dayContainer = `

${weekdays[day]}

` + let dayContainerClasses = 'day-container' + + if (currentDay() == day) { + dayContainerClasses += ' today' + } + + for (const [time, location] of Object.entries(details)) { + let formattedTime = formatTime(time) + let classes = 'time-container' + if (next.day == day && next.time == time) { + classes += ' next-time' + } + + dayContainer += `
  • ${formattedTime}: ${location}
  • ` + } + + table += `
    ` + } + + document.getElementById('championships_table').innerHTML = table +} + +window.addEventListener('load', () => { + fetch('data/championships.json').then((response) => { + response.json().then((json) => { + championships = json + locations = flattenObjectIntoSingleUniqueArray(championships) + + }) + }) + + setInterval(() => { + if (championships.length < 1) { + return + } + + let next = nextChampionship() + + if (lastChampionship != nextChampionship()) { + updateChampionshipDetails(next) + updateChampionshipTable(next) + } + + updateTimer(next) + }, updateFrequency) +}) diff --git a/src/.keep b/src/.keep new file mode 100644 index 0000000..e69de29 diff --git a/style.css b/style.css new file mode 100644 index 0000000..2afcc65 --- /dev/null +++ b/style.css @@ -0,0 +1,92 @@ +:root { + --border: black; + --sso-pink: #e53bb9; + --modal-background: rgba(255, 255, 255, 0.7); + --highlighted-modal-background: rgba(255, 210, 242, 0.7); + --highlighted-row: rgba(229, 59, 185, 0.55); +} + +* { + box-sizing: border-box; +} + +html { + margin: 0; + font-family: sans-serif; + background-color: white; +} + +body { + margin: 0; + padding: 20px; +} + +.background-image { + position: fixed; + inset: 0; + z-index: -1; + background-image: url('src/background.jpg'); + background-repeat: no-repeat; + background-position: center; + background-size: cover; +} + +.timer-container { + min-height: 600px; + padding-top: 50px; +} + +.timer-panel { + text-align: center; + max-width: 40em; + margin: 5px auto; + background-color: var(--modal-background); + border: 1px solid var(--border); + border-radius: 5px; + padding: 5px; +} + +.location-container { + margin-bottom: 0; +} + +.championship { + font-weight: bold; + margin-top: 0; +} + +.days { + display: flex; + flex-flow: row wrap; + gap: 10px; +} + +.day-container { + flex: 1 1 auto; + background-color: var(--modal-background); + border: 1px solid var(--border); + border-radius: 5px; + padding: 5px; +} + +.today { + background-color: var(--highlighted-modal-background); +} + +.day-title { + text-align: center; +} + +.times { + padding-left: 0; +} + +.time-container { + list-style-type: none; + padding: 3px 5px; + border-radius: 5px; +} + +.next-time { + background-color: var(--highlighted-row); +}