KaiOS webapp for InfoFer scraper
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

624 lines
18 KiB

/**
* @type {string}
*/
var trainNumber
/**
* @type {Date}
*/
var date
var groupIndex = null
var yesterday = false
var showKm = false
var trainData = null
/**
* @type {?Date}
*/
var lastSuccessfulFetch = null
/**
* @typedef ArrDep
* @property {string} scheduleTime
* @property {?{delay: number, real: boolean}} status
*
* @typedef Note
* @property {string} kind
*
* @typedef DepartsAsNote
* @type {Note}
* @property {"departsAs"} kind
* @property {string} rank
* @property {string} number
*
* @typedef TrainNumberChangeNote
* @type {Note}
* @property {"trainNumberChange"} kind
* @property {string} rank
* @property {string} number
*
* @typedef DetachingWagonsNote
* @type {Note}
* @property {"detachingWagons"} kind
* @property {string} station
*
* @typedef ReceivingWagonsNote
* @type {Note}
* @property {"receivingWagons"} kind
* @property {string} station
*
* @typedef TrainStop
* @property {string} name
* @property {number} km
* @property {?number} stoppingTime
* @property {?string} platform
* @property {ArrDep} arrival
* @property {ArrDep} departure
* @property {Note[]} notes
*
* @typedef Group
* @property {{from: string; to: string}} route
* @property {{delay: number; station: string; state: "passing" | "arrival" | "departure"} | undefined} status
* @property {TrainStop[]} stations
*/
/**
* @param {{ rank: string; number: string; operator: string; date: string; groups: Group[]; }} data
* @param {?Date} fetchDate
*/
function onTrainData(data, fetchDate) {
var title = document.getElementById('title')
title.textContent = ''
title.appendChild(document.createTextNode('Train '))
trainIdSpan(data.rank, data.number, title)
document.getElementsByTagName('title')[0].textContent = `Train ${data.rank} ${data.number}`
document.getElementById('company').textContent = data.operator
var dateHref = document.createElement('a')
var dateP = document.getElementById('date')
while (dateP.childNodes.length > 0) {
dateP.childNodes[0].remove()
}
dateP.appendChild(dateHref)
dateHref.textContent = data.date
dateHref.href = '#'
dateHref.classList.add('no-a-custom')
dateHref.classList.add('items', 'no-a-custom')
dateHref.addEventListener('click', function (e) {
e.preventDefault()
// Implement date switcher
yesterday = !yesterday
refresh()
})
document.getElementById('loading').classList.add('hidden')
/**
* @type {Group | null}
*/
var group = null;
if (data.groups.length > 1 && groupIndex == null) {
document.getElementById('group-choice').classList.remove('hidden')
document.getElementById('group-choice').focus()
document.getElementById('train-info').classList.add('hidden')
document.getElementsByClassName('rsk')[0].textContent = ''
document.getElementsByClassName('csk')[0].textContent = 'Select'
title.textContent = 'Select Group for '
trainIdSpan(data.rank, data.number, title)
var gc = document.getElementById('group-choice')
while (gc.childNodes.length > 0) {
gc.childNodes[0].remove()
}
for (var i = 0; i < data.groups.length; i++) {
var g = data.groups[i]
var groupLi = document.createElement('li')
gc.append(groupLi)
groupLi.tabIndex = i
groupLi.classList.add('items')
if (i === currentIndex) {
groupLi.focus()
}
(function (i) {
function onAction(e) {
var url = new URL(window.location.toString())
groupIndex = i
if (window.localStorage) {
localStorage.setItem('recent/view-train', JSON.stringify({
trainNumber: trainNumber,
date: date.toISOString(),
groupIndex: groupIndex,
$addDate: new Date().toISOString(),
}))
}
url.searchParams.append('groupIndex', groupIndex)
window.history.pushState({'groupIndex': groupIndex}, '', url.toString( ))
onTrainData(data)
}
groupLi.addEventListener('click', onAction)
groupLi.addEventListener('keypress', function (e) {
if (e.key == 'Enter') {
onAction(e)
}
})
})(i)
var routeP = document.createElement('p')
groupLi.append(routeP)
routeP.classList.add('pri')
routeP.textContent = `${g.route.from}${g.route.to}`
var groupP = document.createElement('p')
groupLi.append(groupP)
groupP.classList.add('thi')
groupP.textContent = i === 0 ? 'Main train' : `Group ${i}`
}
return
}
else if (data.groups.length === 1) {
group = data.groups[0]
}
else {
group = data.groups[groupIndex]
}
document.getElementById('group-choice').classList.add('hidden')
document.getElementById('train-info').classList.remove('hidden')
document.getElementById('train-info').focus()
document.getElementsByClassName('rsk')[0].textContent = 'Refresh'
document.getElementsByClassName('csk')[0].textContent = ''
document.getElementById('route-from').textContent = group.route.from
document.getElementById('route-to').textContent = group.route.to
if (group.status) {
document.getElementById('status').classList.remove('hidden')
var statusDelay = document.getElementById('status-delay')
while (statusDelay.childNodes.length > 0) {
statusDelay.childNodes[0].remove()
}
var delayString = ''
var delayMinutes = group.status.delay
if (delayMinutes === 0) {
delayString = 'On time'
statusDelay.appendChild(document.createTextNode(delayString))
}
else {
var early = false
if (delayMinutes < 0) {
early = true
delayMinutes = -delayMinutes
}
if (delayMinutes >= 60) {
var hours = Math.floor(delayMinutes / 60)
delayMinutes = delayMinutes % 60
delayString += hours.toString()
delayString += ' hour'
if (hours > 1) {
delayString += 's'
}
}
if (delayMinutes > 0) {
if (delayString.length > 0) {
delayString += ' and '
}
delayString += delayMinutes.toString()
delayString += ' minute'
if (delayMinutes > 1) {
delayString += 's'
}
}
delayString += ' '
statusDelay.appendChild(document.createTextNode(delayString))
var kindSpan = document.createElement('span')
statusDelay.appendChild(kindSpan)
if (early) {
kindSpan.textContent = 'early'
kindSpan.classList.add('early')
}
else {
kindSpan.textContent = 'late'
kindSpan.classList.add('late')
}
}
var statusLocation = document.getElementById('status-location')
while (statusLocation.childNodes.length > 0) {
statusLocation.childNodes[0].remove()
}
var stateString = ''
if (group.status.state === 'arrival') {
stateString += 'when arriving at '
}
else if (group.status.state === 'departure') {
stateString += 'when departing from '
}
else if (group.status.state === 'passing') {
stateString += 'while passing through '
}
statusLocation.appendChild(document.createTextNode(stateString))
var stationSpan = document.createElement('span')
statusLocation.appendChild(stationSpan)
stationSpan.textContent = group.status.station
stationSpan.classList.add('station')
}
else {
document.getElementById('status').classList.add('hidden')
}
var stationsDiv = document.getElementById('stations')
while (stationsDiv.childNodes.length > 0) {
stationsDiv.childNodes[0].remove()
}
var separator = document.createElement('h4')
stationsDiv.appendChild(separator)
separator.textContent = 'Stations'
var stationsList = document.createElement('ul')
stationsDiv.appendChild(stationsList)
group.stations.forEach(function (station) {
var stationItem = document.createElement('li')
stationsList.appendChild(stationItem)
stationItem.classList.add('stationItem')
var stationName = document.createElement('p')
stationItem.appendChild(stationName)
stationName.classList.add('pri', 'name')
var stationNameHref = document.createElement('a')
stationName.appendChild(stationNameHref)
stationNameHref.textContent = station.name
stationNameHref.classList.add('items', 'no-a-custom')
var stationUrl = new URL('/view-station.html', window.location.origin)
stationUrl.searchParams.append('station', station.name)
stationUrl.searchParams.append('date', (station.arrival || station.departure).scheduleTime)
stationNameHref.href = stationUrl.toString()
if (station.arrival) {
var stationArrival = document.createElement('div')
stationItem.appendChild(stationArrival)
stationArrival.classList.add('arrival')
var originalArr = document.createElement('p')
stationArrival.appendChild(originalArr)
var originalArrSpan = document.createElement('pre')
originalArr.appendChild(originalArrSpan)
var arrDate = new Date(station.arrival.scheduleTime)
originalArrSpan.textContent = arrDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
originalArr.classList.add('pri')
if (station.arrival.status && station.arrival.status.delay != 0) {
originalArr.classList.remove('pri')
originalArr.classList.add('thi')
originalArrSpan.classList.add('original')
var delaySpanArr = document.createElement('span')
originalArr.appendChild(delaySpanArr)
delaySpanArr.textContent = `${station.arrival.status.delay > 0 ? '+' : ''}${station.arrival.status.delay}`;
delaySpanArr.classList.add(station.arrival.status.delay > 0 ? 'late' : 'early')
delaySpanArr.style.marginLeft = '4px'
var actualArr = document.createElement('p')
stationArrival.appendChild(actualArr)
arrDate.setMinutes(arrDate.getMinutes() + station.arrival.status.delay)
actualArr.classList.add('pri', station.arrival.status.delay > 0 ? 'late' : 'early')
if (!station.arrival.status.real) {
actualArr.classList.add('not-real')
}
var actualArrPre = document.createElement('pre')
actualArr.appendChild(actualArrPre)
actualArrPre.textContent = arrDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
}
}
if (station.departure) {
var stationDeparture = document.createElement('div')
stationItem.appendChild(stationDeparture)
stationDeparture.classList.add('departure')
var originalDep = document.createElement('p')
stationDeparture.appendChild(originalDep)
var depDate = new Date(station.departure.scheduleTime)
var originalDepSpan = document.createElement('pre')
originalDep.appendChild(originalDepSpan)
originalDepSpan.textContent = depDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
originalDep.classList.add('pri')
if (station.departure.status && station.departure.status.delay != 0) {
originalDep.classList.remove('pri')
originalDep.classList.add('thi')
originalDepSpan.classList.add('original')
var delaySpanDep = document.createElement('span')
originalDep.appendChild(delaySpanDep)
delaySpanDep.textContent = `${station.departure.status.delay > 0 ? '+' : ''}${station.departure.status.delay}`;
delaySpanDep.classList.add(station.departure.status.delay > 0 ? 'late' : 'early')
delaySpanDep.style.marginLeft = '4px'
var actualDep = document.createElement('p')
stationDeparture.appendChild(actualDep)
depDate.setMinutes(depDate.getMinutes() + station.departure.status.delay)
actualDep.classList.add('pri', station.departure.status.delay > 0 ? 'late' : 'early')
if (!station.departure.status.real) {
actualDep.classList.add('not-real')
}
var actualDepPre = document.createElement('pre')
actualDep.appendChild(actualDepPre)
actualDepPre.textContent = depDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
}
}
var stationKm = document.createElement('p')
stationItem.appendChild(stationKm)
stationKm.textContent = `${station.km} km`
stationKm.classList.add('thi', 'km')
if (!showKm) {
stationKm.classList.add('hidden')
}
if (station.platform) {
var stationPlatform = document.createElement('p')
stationItem.appendChild(stationPlatform)
stationPlatform.textContent = `platform ${station.platform}`
stationPlatform.classList.add('thi', 'platform')
}
if (station.notes && station.notes.length > 0) {
var stationNotes = document.createElement('div')
stationItem.appendChild(stationNotes)
stationNotes.classList.add('notes')
station.notes.forEach(function (note) {
var noteP = document.createElement('p')
stationNotes.appendChild(noteP)
noteP.classList.add('note', 'thi')
switch (note.kind) {
case 'departsAs': {
noteP.textContent = 'Train departs as '
trainIdSpan(note.rank, note.number, noteP)
break
}
case 'detachingWagons': {
noteP.textContent = `Detaching wagons to ${note.station}`
break
}
case 'receivingWagons': {
noteP.textContent = `Receiving wagons from ${note.station}`
break
}
case 'trainNumberChange': {
noteP.textContent = 'Train changes number to '
trainIdSpan(note.rank, note.number, noteP)
break
}
}
})
}
})
lastSuccessfulFetch = fetchDate || new Date()
}
var refreshStopToken = null
/**
* @returns {Promise<boolean>}
*/
function refresh() {
function reschedule(timeout) {
if (refreshStopToken != null) {
clearTimeout(refreshStopToken)
}
refreshStopToken = setTimeout(function () {
refresh()
}, timeout || 60000)
}
/**
* @type {Date}
*/
var reqDate = new Date(date.valueOf())
if (yesterday) {
reqDate.setDate(reqDate.getDate() - 1)
}
reqDate.setMinutes(0, 0, 0)
return fetch(
`https://scraper.infotren.dcdev.ro/v3/trains/${trainNumber}?date=${reqDate.toISOString()}`,
{
cache: 'no-store',
},
).then(function (response) {
if (!response.ok) {
// Check in 10 seconds if server returned error
reschedule(10000)
return
}
var cacheDate = response.headers.get('SW-Cached-At')
return response.json().then(function (data) {
data['$cacheDate'] = cacheDate
return data
})
}).then(function (response) {
if (!response) {
return
}
var cacheDate = response['$cacheDate']
if (cacheDate) {
cacheDate = new Date(cacheDate)
}
var success = !cacheDate
trainData = response
onTrainData(response, cacheDate)
// Check in 1 seconds if network error
reschedule(success ? undefined : 1000)
return success
}).catch(function (e) {
// Check in 1 second if network error
reschedule(1000)
throw e
})
}
window.addEventListener('unload', function (e) {
if (refreshStopToken != null) {
clearTimeout(refreshStopToken)
}
})
function rsk() {
function changeRskText(newText) {
document.querySelectorAll('.rsk').forEach(function (elem) {
elem.textContent = newText
})
}
changeRskText('Refreshing...')
refresh().catch(function () { return false}).then(function (success) {
if (!success) {
changeRskText('Refreshing failed')
setTimeout(function (_) {
changeRskText('Refresh')
}, 3000)
}
else {
changeRskText('Refresh')
}
})
}
window.addEventListener('popstate', function (e) {
groupIndex = null
if (trainData) {
onTrainData(trainData)
}
else {
refresh()
}
})
window.addEventListener('load', function (e) {
if (!new URL(window.location.href).searchParams.has('train')) {
window.history.back()
this.setTimeout(function () {
var url = new URL(window.location.href)
url.pathname = 'train.html'
window.location.href = url.toString()
}, 100)
}
var sp = new URL(window.location.href).searchParams
trainNumber = sp.get('train')
date = sp.has('date') ? new Date(sp.get('date')) : new Date()
groupIndex = sp.has('groupIndex') ? parseInt(sp.get('groupIndex')) : null
if (window.localStorage) {
var oldRecent = localStorage.getItem('recent/view-train')
if (oldRecent) {
oldRecent = JSON.parse(oldRecent)
}
localStorage.setItem('recent/view-train', JSON.stringify({
trainNumber: trainNumber,
date: date.toISOString(),
groupIndex: oldRecent && oldRecent.trainNumber === trainNumber ? oldRecent.groupIndex : undefined,
$addDate: new Date().toISOString(),
}))
}
document.querySelectorAll('.rsk').forEach(function (rskElem) {
rskElem.addEventListener('click', function (e) {
rsk()
})
})
if (navigator.canShare && navigator.canShare({ url: '' })) {
document.getElementById('title').addEventListener('click', function () {
navigator.share({ url: '' });
})
}
var content = document.getElementsByClassName('content')[0]
content.focus()
content.addEventListener('keydown', function (e) {
switch (e.key) {
// case 'ArrowUp':
// content.scrollBy(0, -50)
// break
// case 'ArrowDown':
// content.scrollBy(0, 50)
// break
case 'SoftRight':
rsk()
break
case '1':
date.setDate(date.getDate() - 1)
refresh()
break
case '3':
date.setDate(date.getDate() + 1)
refresh()
break
case '7':
showKm = !showKm
document.querySelectorAll('.km').forEach(function (kmItem) {
if (showKm) {
kmItem.classList.remove('hidden')
}
else {
kmItem.classList.add('hidden')
}
})
break
default:
console.log(e.key)
}
})
refresh()
setInterval(function () {
if (!lastSuccessfulFetch) {
return
}
var millis = new Date() - lastSuccessfulFetch
var secs = Math.floor(millis / 1000)
var timeStr = ''
if (secs / 3600 >= 1) {
timeStr += `${Math.floor(secs / 3600)}h`
secs = secs % 3600
}
if (secs / 60 >= 1) {
timeStr += `${Math.floor(secs / 60)}m`
secs = secs % 60
}
if (secs >= 1) {
timeStr += `${Math.floor(secs)}s`
}
if (!timeStr) {
document.querySelectorAll('.lsk').forEach(function (elem) {
elem.textContent = 'Last refreshed now'
elem.classList.add('last-refreshed')
})
}
else {
document.querySelectorAll('.lsk').forEach(function (elem) {
elem.textContent = `Last refreshed ${timeStr} ago`
elem.classList.add('last-refreshed')
})
}
}, 500)
if (this.localStorage && !this.localStorage.getItem('info-yesterday')) {
this.alert("New feature: You can now view yesterday's train by selecting the date!")
this.localStorage.setItem('info-yesterday', 'true')
}
})