Files
dernier-metro/lol/tcl.js
T
2026-06-13 23:42:04 +02:00

201 lines
6.1 KiB
JavaScript

const DAY_IN_MS = 8.64e+7;
const MIN_TIME_BETWEEN_DAYS = 2 * 60 * 60000 // 2 hours
class StopId {
constructor(stopIdString){
let [lineId, direction, stopId] = stopIdString.split(/\//)
this.lineId = lineId
this.stopId = stopId
this.direction = direction
}
}
async function getNextTripJSON(stop_id){
let res = await fetch(`/api/interface/tcl/next-trips/stops/${encodeURIComponent(stop_id.stopId)}/${encodeURIComponent(stop_id.lineId)}/${stop_id.direction}`);
if(!res.ok){
throw new Error(`Server responded with ${res.status} ${res.statusText}`)
}
let result = await res.json();
if(!result.data){
throw new Error(`Stop ${stop_id.stopId} not found`)
}
return result.data[0]
}
async function getStopsJSON(stop_id){
let res = await fetch(`/api/interface/tcl/lines/${encodeURIComponent(stop_id.lineId)}/stops`);
if(!res.ok){
throw new Error(`Server responded with ${res.status} ${res.statusText}`)
}
let result = await res.json();
if(!result.data){
throw new Error(`Line ${stop_id.lineId} not found`)
}
return result.data
}
async function getTimetableJSON(stop_id, date){
let res = await fetch(`/api/interface/tcl/timetables/${encodeURIComponent(stop_id.stopId)}/${encodeURIComponent(stop_id.lineId)}/${stop_id.direction}?date=${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`);
if(!res.ok){
throw new Error(`Server responded with ${res.status} ${res.statusText}`)
}
let result = await res.json();
if(!result.data){
throw new Error(`Timetable for ${stop_id.lineId} not found`)
}
return result.data
}
async function getLineDetailsJSON(stop_id){
let res = await fetch(`/api/interface/tcl/lines/${encodeURIComponent(stop_id.lineId)}`);
if(!res.ok){
throw new Error(`Server responded with ${res.status} ${res.statusText}`)
}
let result = await res.json();
if(!result.data){
throw new Error(`Line ${stop_id.lineId} not found`)
}
return result.data
}
function getIconURL(stop_id){
return new URL(`/api/valkyrie/assets/lines/${stop_id.lineId}.svg?type=image%2Fsvg%2Bxml`, window.location).toString()
}
async function getLineDetails(stop_id){
let line_json = await getLineDetailsJSON(stop_id)
let route = line_json.routes.find(it => it.direction == stop_id.direction)
let line = {
id: stop_id.lineId,
displayName: route?.name,
picto: getIconURL(stop_id),
name: line_json.code,
direction: route?.name,
timetable: null
}
return line
}
async function getTimetable(stop_id, date){
let timetable_json = await getTimetableJSON(stop_id, date)
let timetable = []
for(let scheduled_time of timetable_json.scheduleTimes){
let time = new Date(scheduled_time.dateTime)
timetable.push({
displayHours: time.getHours().toString().padStart(2, "0"),
displayMinutes: time.getMinutes().toString().padStart(2, "0"),
displayTime: `${time.getHours().toString().padStart(2, "0")}h${time.getMinutes().toString().padStart(2, "0")}`,
time
})
}
return timetable
}
/**
* Get next Bus/Metro/Tram passage of given stop description
* @param {String} stop_description Stop description string
*
* To get stop description go to https://carte-interactive.tcl.fr/public-transport/lines/
* then find your line and your stop in this line.
* Your URL must look like something like this
* https://carte-interactive.tcl.fr/public-transport/lines/line:SYTNEX:C/forward/stop_point:SYTNEX:10787
* Stop description is everything after "lines/" ("line:SYTNEX:C/forward/stop_point:SYTNEX:10787" in example above)
*/
export async function getNextPassage(stop_description, options = {
timetable: +2 // Today and tomorrow
}){
let stopId = new StopId(stop_description);
let proms = [
getLineDetails(stopId),
getNextTripJSON(stopId),
getStopsJSON(stopId),
Promise.resolve([])
]
let timeTableAmount = options?.timetable || +2;
if(timeTableAmount){
for(let dayOffset = 0; dayOffset<timeTableAmount; dayOffset++){
let now = new Date(Date.now()+(dayOffset*DAY_IN_MS))
proms.push(await getTimetable(stopId, now))
}
}
let [line, next_trip, line_stops, ...all_timetables] = await Promise.all(proms);
let line_timetables = []
for(let timetable of all_timetables){
line_timetables.push(...timetable)
}
line.timetable = line_timetables
line.getLastPassageTime = getLastPassageTime.bind(line)
let stop = line_stops.find(it => it.id == stopId.stopId)
let time;
if(next_trip.schedules){
let next_schedule = next_trip.schedules.find(it => (new Date(it.dateTime).getTime() - Date.now()) > 5*60*1000 )
if(!next_schedule) {
next_schedule = next_trip.schedules[next_trip.schedules.length - 1]
}
if(next_schedule){
time = new Date(next_schedule.dateTime)
}
}
let nextPassage = {
line,
displayName: stop?.name,
displayTime: "<unsupported>",
time: next_trip.schedules?.[0] ? new Date(next_trip.schedules?.[0].dateTime) : null
}
nextPassage.getLastPassageTime = getLastPassageTime.bind(nextPassage)
return nextPassage
}
export async function getVelovBikeState(station_id){
let res = await fetch(`/api/interface/tcl/realtime/navitia/${decodeURIComponent(station_id)}`)
if(!res.ok){
throw new Error(`Server responded with ${res.status} ${res.statusText}`)
}
return await res.json()
}
function getLastPassageTime(){
let timetable = this.timetable || this.line.timetable
if(!timetable){
return
}
let now = new Date()
let last_passage = timetable
.find((it, i) => {
if((timetable[i+1]) && it.time.getTime() > now && (it.time.getTime() + MIN_TIME_BETWEEN_DAYS) < timetable[i+1].time.getTime()){
return true
} else {
return false
}
})
return last_passage
}