this commit is a lie

This commit is contained in:
2023-12-02 00:48:33 +01:00
commit 65d9a8e282
8 changed files with 419 additions and 0 deletions
+6
View File
@@ -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
+33
View File
@@ -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.
+65
View File
@@ -0,0 +1,65 @@
<?php
// much faster than file()
$lines = explode(
"\n",
str_replace(
["\r\n", "\r"],
"\n",
file_get_contents('championships.txt')
)
);
$location = "Unknown";
$championships_by_time = [];
// get the data into a per-event format
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
if (str_contains($line, ":")) {
$day_and_times = explode(": ", $line);
$day = trim($day_and_times[0]);
foreach (explode(",", trim($day_and_times[1])) as $time) {
$championships_by_time[] = [$day, trim($time), $location];
}
}
else {
$location = str_replace(["The ", " Championship"], "", $line);
}
}
// native JS expects dates in this format
$days = [
'Monday' => 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);
+61
View File
@@ -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
Executable
+24
View File
@@ -0,0 +1,24 @@
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>SSO Timer</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" type="image/png" sizes="32x32" href="src/favicon-32x32.png">
</head>
<body>
<div class="background-image"></div>
<div class="timer-container">
<div class="timer-panel">
<h2>Next Up</h2>
<h3 class="location-container">The <span id="location">...</span> Championship</h2>
<p class="championship">at <span id="time">00:00</span></p>
<p class="remaining">Remaining: <span id="timer">00:00:00</span></p>
</div>
</div>
<div class="days" id="championships_table"></div>
<script src="script.js"></script>
</body>
</html>
+138
View File
@@ -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 = `<h1 class="day-title">${weekdays[day]}</h1>`
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 += `<li class="${classes}"><b class="time">${formattedTime}</b>: ${location}</li>`
}
table += `<div class="${dayContainerClasses}"><ul class="times">${dayContainer}</ul></div>`
}
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)
})
View File
+92
View File
@@ -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);
}