Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kitinerary for openSUSE:Factory checked in at 2026-10-10 14:56:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kitinerary (Old) and /work/SRC/openSUSE:Factory/.kitinerary.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kitinerary" Sat Oct 10 14:56:25 2026 rev:102 rq:1383485 version:26.08.2 Changes: -------- --- /work/SRC/openSUSE:Factory/kitinerary/kitinerary.changes 2026-09-12 21:18:18.945879196 +0200 +++ /work/SRC/openSUSE:Factory/.kitinerary.new.3698736/kitinerary.changes 2026-10-10 14:56:50.734619326 +0200 @@ -1,0 +2,18 @@ +Wed Oct 7 07:03:32 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 26.08.2 + * New bugfix release + * For more details please see: + * https://kde.org/announcements/gear/26.08.2/ +- Changes since 26.08.1: + * Fix SBB QR code matching pattern to cover current Bern Tickets + * Add Snållåget PDF ticket extractor script + * Adapt Entur extractor to a new/alternative ticket PDF layout + * Fix reading date values from JSON with Qt 6.13 + * accor: Prefer checkin/out dates from JSON-LD data + * feat: support Spanish bus tickets from ALSA + * Add extractor for Delta Air Lines emails + * Add extractor for Frontier Airlines emails + * Support "English" Ouigo tickets + +------------------------------------------------------------------- Old: ---- kitinerary-26.08.1.tar.xz kitinerary-26.08.1.tar.xz.sig New: ---- kitinerary-26.08.2.tar.xz kitinerary-26.08.2.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kitinerary.spec ++++++ --- /var/tmp/diff_new_pack.QwKC7T/_old 2026-10-10 14:56:51.511651814 +0200 +++ /var/tmp/diff_new_pack.QwKC7T/_new 2026-10-10 14:56:51.512651856 +0200 @@ -18,11 +18,11 @@ %define kf6_version 6.27.0 %define qt6_version 6.9.0 -%define kpim6_version 6.8.1 +%define kpim6_version 6.8.2 %bcond_without released Name: kitinerary -Version: 26.08.1 +Version: 26.08.2 Release: 0 Summary: Data model and extraction system for travel reservations License: LGPL-2.1-or-later ++++++ kitinerary-26.08.1.tar.xz -> kitinerary-26.08.2.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/CMakeLists.txt new/kitinerary-26.08.2/CMakeLists.txt --- old/kitinerary-26.08.1/CMakeLists.txt 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/CMakeLists.txt 2026-10-06 05:08:07.000000000 +0200 @@ -7,10 +7,10 @@ # KDE Application Version, managed by release script set (RELEASE_SERVICE_VERSION_MAJOR "26") set (RELEASE_SERVICE_VERSION_MINOR "08") -set (RELEASE_SERVICE_VERSION_MICRO "1") +set (RELEASE_SERVICE_VERSION_MICRO "2") set (RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") -set(PIM_VERSION "6.8.1") +set(PIM_VERSION "6.8.2") project(KItinerary VERSION ${PIM_VERSION}) set(QT_REQUIRED_VERSION "6.7.0") @@ -49,7 +49,7 @@ endif() set(KMIME_VERSION "6.7.0") -set(PIM_PKPASS "6.8.1") +set(PIM_PKPASS "6.8.2") find_package(KF6Mime ${KF_MIN_VERSION} CONFIG) if(NOT TARGET KF6::Mime) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/po/ru/kitinerary6.po new/kitinerary-26.08.2/po/ru/kitinerary6.po --- old/kitinerary-26.08.1/po/ru/kitinerary6.po 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/po/ru/kitinerary6.po 2026-10-06 05:08:07.000000000 +0200 @@ -112,12 +112,12 @@ #: calendarhandler.cpp:351 #, kde-format msgid "Check-in: %1" -msgstr "Регистрация заезда: %1" +msgstr "Заезд: %1" #: calendarhandler.cpp:354 #, kde-format msgid "Check-out: %1" -msgstr "Регистрация отъезда: %1" +msgstr "Выезд: %1" #: calendarhandler.cpp:357 #, kde-format @@ -137,7 +137,7 @@ #: calendarhandler.cpp:413 #, kde-format msgid "Entrance for %1" -msgstr "Имя: %1" +msgstr "Вход на %1" #: calendarhandler.cpp:460 #, kde-format @@ -152,7 +152,7 @@ #: calendarhandler.cpp:481 calendarhandler.cpp:513 #, kde-format msgid "Under name: %1" -msgstr "Имя для резервирования: %1" +msgstr "Имя для бронирования: %1" #: calendarhandler.cpp:491 #, kde-format diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/cli/org.kde.kitinerary-extractor.appdata.xml new/kitinerary-26.08.2/src/cli/org.kde.kitinerary-extractor.appdata.xml --- old/kitinerary-26.08.1/src/cli/org.kde.kitinerary-extractor.appdata.xml 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/cli/org.kde.kitinerary-extractor.appdata.xml 2026-10-06 05:08:07.000000000 +0200 @@ -114,7 +114,7 @@ <p xml:lang="pt">Extracção pela linha de comandos de dados de itinerários, para se integrar com aplicações de terceiros.</p> <p xml:lang="pt-BR">Extrator em linha de comando para dados de itinerários, para integração em aplicativos de terceiros.</p> <p xml:lang="ro">Extractor în linie de comandă pentru date despre itinerare, pentru integrare în aplicații de la terți.</p> - <p xml:lang="ru">Инструмент командной стоки для извлечения данных маршрутов для использования со сторонними приложениями.</p> + <p xml:lang="ru">Инструмент командной строки для извлечения данных маршрутов для использования со сторонними приложениями.</p> <p xml:lang="sa">यात्रासूचनादत्तांशस्य कृते आदेशपङ्क्तिनिष्कासकः, तृतीयपक्षीय-अनुप्रयोगेषु एकीकरणाय ।</p> <p xml:lang="sk">Extraktor cez príkazový riadok pre dáta itinerára na integráciu do aplikácií tretích strán.</p> <p xml:lang="sl">Ekstraktor ukazne vrstice za potovalne podatke za integracijo aplikacije tretje osebe.</p> @@ -135,6 +135,7 @@ </categories> <launchable type="desktop-id">org.kde.kitinerary-extractor.desktop</launchable> <releases> + <release version="6.8.2" date="2026-10-08"/> <release version="6.8.1" date="2026-09-10"/> <release version="6.8.0" date="2026-08-20"/> <release version="6.7.3" date="2026-07-02"/> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/jsonlddocument.cpp new/kitinerary-26.08.2/src/lib/jsonlddocument.cpp --- old/kitinerary-26.08.1/src/lib/jsonlddocument.cpp 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/jsonlddocument.cpp 2026-10-06 05:08:07.000000000 +0200 @@ -187,7 +187,7 @@ } return v.toString(); case QMetaType::QDate: - return QDate::fromString(v.toString(), Qt::ISODate); + return QDate::fromString(QStringView(v.toString()).left(10), Qt::ISODate); case QMetaType::QDateTime: { QDateTime dt; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/accor.js new/kitinerary-26.08.2/src/lib/scripts/accor.js --- old/kitinerary-26.08.1/src/lib/scripts/accor.js 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/scripts/accor.js 2026-10-06 05:08:07.000000000 +0200 @@ -20,10 +20,11 @@ "dd.MM.yyyyHH:mm", "dd.MM.yyyy", "dd/MM/yyyyHH:mm", "dd/MM/yyyy", "MMM. dd, yyyyHH:mm", "MMM. dd, yyyy", - "dd MMM yyyyHH:mm", "dd MMM yyyy" + "dd MMM yyyyHH:mm", "dd MMM yyyy", + "yyyy-MM-ddHH:mm", "yyyy-MM-dd" ]; - res.checkinTime = JsonLd.toDateTime(dt[1] + (times ? times[1] : ""), formats, "en"); - res.checkoutTime = JsonLd.toDateTime(dt[2] + (times ? times[2] : ""), formats, "en"); + res.checkinTime = JsonLd.toDateTime((res.checkinDate ? res.checkinDate : dt[1]) + (times ? times[1] : ""), formats, "en"); + res.checkoutTime = JsonLd.toDateTime((res.checkoutDate ? res.checkoutDate : dt[2]) + (times ? times[2] : ""), formats, "en"); let hotelNode = html.eval('//table//table//table[@class="table-full"]'); if (!hotelNode || hotelNode.length === 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/alsa.js new/kitinerary-26.08.2/src/lib/scripts/alsa.js --- old/kitinerary-26.08.1/src/lib/scripts/alsa.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/alsa.js 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,113 @@ +/* + SPDX-FileCopyrightText: 2026 Stephan Seitz <[email protected]> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +function decodeBarcode(page, barcode) { + // top left quarter page contains price, billing info and general terms and conditions + var billingInfo = page.textInRect(0.0, 0.0, 0.5, 0.5); + // top right quarter page contains all travel info + var travelInfo = page.textInRect(0.5, 0.0, 1.0, 0.5); + + var departureAddressText = page.textInRect(0.5, 0.18, 0.75, 0.25).split(",\n"); + var arrivalAddressText = page.textInRect(0.75, 0.18, 1.0, 0.25).split(",\n"); + + // example Madrid->Bilbao ASP=N202608261600&Q=:3S000000**B203-1-999-32337258-1**$;203-1-999-32337258-1-1*VAC-157*3885*480200001*280790001*202608252007*202608261600 + const code = barcode.split('*'); + if (code.length != 11 || !barcode.startsWith("ASP=")){ + console.log("Barcode didn't have the expected format:", barcode); + return null; + } + + // The departureCode and destinationCode in the barcode can only be decoded using a data from INE (derived from https://www.ine.es/daco/daco42/codmun/codmun08/08codmun.xls, CC-BY-4.0) + // We decided to not embed this mapping here, see discussion in https://invent.kde.org/pim/kitinerary/-/merge_requests/236 + + const departureTime = JsonLd.toDateTime(code[0].match(/ASP=N(\d{12}).*/)[1], ["yyyyMMddhhmm"], "en"); + // const empty = code[1]; + const ticketId = code[2]; + // const empty2 = code[3]; + // const ticketId = code[4]; + // const concessionCode = code[5]; + const lineNumber = code[6]; + const departureCode = code[7]; + const destinationCode = code[8]; + //const bookingTime = code[9]; + //const departureTimeAgain = code[10]; + + // Arrival time only in text + const dates = travelInfo.match(/(\d+\s\w+\s\d{4})\s+(\d+\s\w+\s\d{4})/); + const times = travelInfo.match(/(\d+:\d+)\s+(\d+:\d+)/); + + var arrivalTime = departureTime; + if (dates && times) { + arrivalTime = JsonLd.toDateTime(dates[2] + " " + times[2], ["dd MMMM yyyy hh:mm"], ["es", "en"]); + } + + var res = JsonLd.newBusReservation(); + res.reservationFor.departureTime = departureTime; + res.reservationFor.arrivalTime = arrivalTime; + res.reservationFor.provider.name = "ALSA"; + + res.reservationId = ticketId; + res.reservationFor.busNumber = lineNumber; + + res.reservationFor.departureBusStop.identifier = "alsa:" + departureCode; + if (departureAddressText.length >= 3) { + const busStopName = departureAddressText[0].replace(/,$/, "").trim(); + const streetAddress = departureAddressText[1].replace(/,$/, "").trim(); + const city = departureAddressText[2].split("\n\n")[0].replace(/,$/, "").trim(); + + // e.g. Madrid (INTERCAMBIADOR DE AUTOBUSES) + res.reservationFor.departureBusStop.name = city + " (" + busStopName + ")"; + res.reservationFor.departureBusStop.address.streetAddress = streetAddress; + res.reservationFor.departureBusStop.address.addressLocality = city; + } + + res.reservationFor.arrivalBusStop.identifier = "alsa:" + destinationCode; + if (arrivalAddressText.length >= 3) { + const busStopName = arrivalAddressText[0].replace(/,$/, "").trim(); + const streetAddress = arrivalAddressText[1].replace(/,$/, "").trim(); + const city = arrivalAddressText[2].split("\n\n")[0].replace(/,$/, "").trim(); + + res.reservationFor.arrivalBusStop.name = city + " (" + busStopName + ")"; + res.reservationFor.arrivalBusStop.address.streetAddress = streetAddress; + res.reservationFor.arrivalBusStop.address.addressLocality = city; + } + + const totalPrice = billingInfo.match(/Importe total: (\d+,\d+€)/); + if (totalPrice) { + res.totalPrice = totalPrice[1]; + } + + const name = travelInfo.match(/^(.*)\s,\s(.*)$/m); + if (name) { + res.underName.familyName = name[1].trim(); + res.underName.givenName = name[2].trim(); + } + + const seatInfo = travelInfo.match(/Autobús\s+Asiento\s+(\d+)\s+(\d+)/); + if (seatInfo) { + res.reservedTicket.ticketedSeat.seatSection = seatInfo[1]; // coach number + res.reservedTicket.ticketedSeat.seatNumber = seatInfo[2]; + } + + res.reservedTicket.ticketToken = "qrCode:" + barcode; + + return res; +} + +function parsePdfTicket(pdf, node, triggerNode) +{ + let reservations = []; + for (var i = 0; i < pdf.pageCount; ++i) { + var page = pdf.pages[i]; + var nextBarcode = null; + var images = page.images; + for (var j = 0; j < images.length && !nextBarcode; ++j) { + nextBarcode = Barcode.decodeQR(images[j]); + if (nextBarcode) + reservations.push(decodeBarcode(page, nextBarcode)); + } + } + return reservations +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/alsa.json new/kitinerary-26.08.2/src/lib/scripts/alsa.json --- old/kitinerary-26.08.1/src/lib/scripts/alsa.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/alsa.json 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,14 @@ +[ + { + "filter": [ + { + "match": "https://www.alsa.es/", + "mimeType": "text/plain", + "scope": "Descendants" + } + ], + "function": "parsePdfTicket", + "mimeType": "application/pdf", + "script": "alsa.js" + } +] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/delta.js new/kitinerary-26.08.2/src/lib/scripts/delta.js --- old/kitinerary-26.08.1/src/lib/scripts/delta.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/delta.js 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,68 @@ +/* + SPDX-FileCopyrightText: 2026 leo vriska <[email protected]> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +function main(html) { + const reservations = []; + + const [confirmationNumber] = html.eval("//b[text()='Confirmation Number']/../../div//span"); + + const passengers = html.eval("//strong[text()='Passenger Info']/../../..").map(info => ({ + name: /Name: (.+)/.exec(info.eval(".//td[1]")[0]?.content)[1], + seats: info.eval(".//tr/td[not(*)][2]").map(x => x.content), + })); + + // The itinerary table only contains airport names, but the baggage allowance table uses IATA codes. + const baggageHeaders = html.eval("//strong[text()='Checked Bag Allowance']/../../../tr[count(td[b])=2]"); + const segments = html.eval("//b[text()='DEPART']/../../following-sibling::tr[1]"); + + for (let i = 0; i < segments.length; i++) { + const baggageHeader = baggageHeaders[i]; + const segment = segments[i]; + + const [date, airports] = baggageHeader.eval(".//b").map(x => x.content); + const [departureAirport, arrivalAirport] = airports.split("-"); + + const [flight, departure, arrival] = segment.eval("./td"); + + const [, airline, flightNumber] = /^(.+?) ([0-9]+)\*?$/m.exec(flight.content); + + const [departureAirportName, departureTime] = departure.content.split(/\s+/g); + const [arrivalAirportName, arrivalTime] = arrival.content.split(/\s+/g); + + const dateFormat = "ddd dd MMM yyyy hh:mmAP"; + const departureDatetime = JsonLd.toDateTime(date + " " + departureTime, dateFormat, "en"); + const arrivalDatetime = JsonLd.toDateTime(date + " " + arrivalTime, dateFormat, "en"); + + for (const {name, seats} of passengers) { + const res = JsonLd.newFlightReservation(); + + res.reservationFor.airline.name = airline; + if (airline === "DELTA") res.reservationFor.airline.iataCode = "DL"; + + res.reservationNumber = confirmationNumber?.content; + + res.reservationFor.flightNumber = flightNumber; + + res.reservationFor.departureTime = departureDatetime; + res.reservationFor.departureAirport.iataCode = departureAirport; + res.reservationFor.departureAirport.name = departureAirportName; + res.reservationFor.arrivalTime = arrivalDatetime; + res.reservationFor.arrivalAirport.iataCode = arrivalAirport; + res.reservationFor.arrivalAirport.name = arrivalAirportName; + + res.underName.name = name; + res.airplaneSeat = seats[i]; + + reservations.push(res); + } + } + + const [price] = html.eval("//b[text()='METHOD OF PAYMENT']/../../following-sibling::tr[1]/td/b"); + + if (price) + ExtractorEngine.extractPrice(price.recursiveContent, reservations); + + return reservations; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/delta.json new/kitinerary-26.08.2/src/lib/scripts/delta.json --- old/kitinerary-26.08.1/src/lib/scripts/delta.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/delta.json 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "filter": [ + { + "field": "From", + "match": "@t.delta.com", + "mimeType": "message/rfc822", + "scope": "Ancestors" + } + ], + "function": "main", + "mimeType": "text/html", + "script": "delta.js" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/entur.js new/kitinerary-26.08.2/src/lib/scripts/entur.js --- old/kitinerary-26.08.1/src/lib/scripts/entur.js 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/scripts/entur.js 2026-10-06 05:08:07.000000000 +0200 @@ -6,18 +6,19 @@ let res = JsonLd.newTrainReservation(); res.reservationNumber = text.match(/(?:Reference|Referanse): ([A-Z0-9]+)/)[1]; - res.reservedTicket.name = text.match(/(?:Ticket|Billett): *(\S.+)\n/)[1]; + const ticketName = text.match(/(?:Ticket|Billett): *(\S.+)\n/); + res.reservedTicket.name = ticketName ? ticketName[1] : undefined; res.reservedTicket.ticketToken = "qrCode:" + barcode.content; - const dt = text.match(/(?:Valid|Gyldig): *\S.*?\S (\w{3} \d{2}, \d{4}|\d{2}\. \S+ \d{4})/); - const trip = text.match(/(\d\d:\d\d) +(\S.*?\S) +(?:Train|Tog) - (.*)\n *(\d\d:\d\d) +(\S.*?\S) /); - res.reservationFor.departureTime = JsonLd.toDateTime(dt[1] + trip[1], ["MMM dd, yyyyhh:mm", "dd. MMMM yyyyhh:mm"], ["en", "no"]); + const dt = text.match(/(?:Valid|Gyldig):? *(?:\S.*?\S )?(\w{3} \d{2}, \d{4}|\d{1,2}\. \S+\.? \d{4})/); + const trip = text.match(/(\d\d:\d\d) +(\S.*?\S) +(?:(?:Train|Tog) - (.*)|\S.*\n *(\S.*), Tog.*\n *Vogn.*)\n *(\d\d:\d\d) +(\S.*?\S)(?: |\n)/); + res.reservationFor.departureTime = JsonLd.toDateTime(dt[1] + trip[1], ["MMM dd, yyyyHH:mm", "dd. MMMM yyyyHH:mm", "d. MMM. yyyyHH:mm"], ["en", "no"]); res.reservationFor.departureStation.name = trip[2]; - res.reservationFor.trainNumber = trip[3]; - res.reservationFor.arrivalTime = JsonLd.toDateTime(dt[1] + trip[4], ["MMM dd, yyyyhh:mm", "dd. MMMM yyyyhh:mm"], ["en", "no"]); - res.reservationFor.arrivalStation.name = trip[5]; + res.reservationFor.trainNumber = trip[3] ? trip[3] : trip[4]; + res.reservationFor.arrivalTime = JsonLd.toDateTime(dt[1] + trip[5], ["MMM dd, yyyyHH:mm", "dd. MMMM yyyyHH:mm", "d. MMM. yyyyHH:mm"], ["en", "no"]); + res.reservationFor.arrivalStation.name = trip[6]; - const seat = text.match(/(?:Car|Vogn) (\S+) - (?:space|sete) (\S.+)\n/); + const seat = text.match(/(?:Car|Vogn) (\S+)(?: -|,) (?:[Ss]pace|[Ss]ete) (\S.+)\n/); if (seat) { res.reservedTicket.ticketedSeat.seatSection = seat[1]; res.reservedTicket.ticketedSeat.seatNumber = seat[2]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/extractors.qrc new/kitinerary-26.08.2/src/lib/scripts/extractors.qrc --- old/kitinerary-26.08.1/src/lib/scripts/extractors.qrc 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/scripts/extractors.qrc 2026-10-06 05:08:07.000000000 +0200 @@ -24,6 +24,8 @@ <file>airdo.js</file> <file>airfrance.json</file> <file>airfrance.js</file> + <file>alsa.js</file> + <file>alsa.json</file> <file>amadeus.json</file> <file>amadeus.js</file> <file>americanairlines.json</file> @@ -107,6 +109,8 @@ <file>czechrailways.js</file> <file>dav.json</file> <file>dav.js</file> + <file>delta.json</file> + <file>delta.js</file> <file>deutschebahn.json</file> <file>deutschebahn.js</file> <file>deutschebahn-online-ticket.js</file> @@ -177,6 +181,8 @@ <file>flibco.js</file> <file>flixbus.json</file> <file>flixbus.js</file> + <file>flyfrontier.json</file> + <file>flyfrontier.js</file> <file>frs.json</file> <file>frs.js</file> <file>gastronovi.json</file> @@ -358,6 +364,8 @@ <file>slovak-lines.js</file> <file>scandlines.json</file> <file>scandlines.js</file> + <file>snalltaget.json</file> + <file>snalltaget.js</file> <file>sncb.json</file> <file>sncb.js</file> <file>sncf.json</file> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/flyfrontier.js new/kitinerary-26.08.2/src/lib/scripts/flyfrontier.js --- old/kitinerary-26.08.1/src/lib/scripts/flyfrontier.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/flyfrontier.js 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,75 @@ +/* + SPDX-FileCopyrightText: 2026 leo vriska <[email protected]> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +function main(html) { + const reservations = []; + + const [bookingCode] = html.eval("//div[@class='confirmationcode-fg-pill-copy']"); + + const [passengerDetails] = html.eval("//td[normalize-space(text())='Passenger Details']/ancestor::table[@align='center'][1]/following-sibling::table[1]"); + + // FIXME: how does this work for multiple passengers? + const name = passengerDetails?.eval(".//td[not(*)]")?.[0]; + + // FIXME: how does this work for one-way and multi-city? + const outboundSeats = passengerDetails?.eval(".//tr[td[normalize-space(text())='Outbound Seats']]/following-sibling::tr/td")?.[0]; + const returnSeats = passengerDetails?.eval(".//tr[td[normalize-space(text())='Return Seats']]/following-sibling::tr/td")?.[0]; + const seats = [outboundSeats, returnSeats]; + + const dates = html.eval("//td[@class='itinerary-cl-sectionbar']/descendant::td[not(*)][last()]").map(x => x.content.split(': ').pop()); + const segments = html.eval("//div[normalize-space(text())='Departs']/ancestor::table[2]"); + + for (let i = 0; i < segments.length; i++) { + const date = dates[i]; + const seat = seats[i]; + const segment = segments[i]; + + const res = JsonLd.newFlightReservation(); + + // Frontier does not have any interline agreements + res.reservationFor.airline.iataCode = "F9"; + res.reservationFor.airline.name = "Frontier Airlines"; + + res.reservationNumber = bookingCode?.content; + + const [flightNumber] = segment.eval(".//*[(self::td or self::div) and normalize-space(text()[1])='Flight' and starts-with(normalize-space(text()[2]), '#')]"); + + if (flightNumber) + res.reservationFor.flightNumber = flightNumber.recursiveContent.split("#")[1]; + + const [departure, departureAirportName] = segment.eval(".//div[preceding-sibling::div[normalize-space(text())='Departs']]"); + + if (departure) { + const [departureAirport, departureTime] = departure.recursiveContent.split("\n"); + res.reservationFor.departureTime = JsonLd.toDateTime(date + " " + departureTime, "MMMM d, yyyy h:mm ap", "en"); + res.reservationFor.departureAirport.iataCode = departureAirport; + res.reservationFor.departureAirport.name = departureAirportName?.content; + } + + const [arrival, arrivalAirportName] = segment.eval(".//div[preceding-sibling::div[normalize-space(text())='Arrives']]"); + + if (arrival) { + const [arrivalAirport, arrivalTime] = arrival.recursiveContent.split("\n"); + res.reservationFor.arrivalTime = JsonLd.toDateTime(date + " " + arrivalTime, "MMMM d, yyyy h:mm ap", "en"); + res.reservationFor.arrivalAirport.iataCode = arrivalAirport; + res.reservationFor.arrivalAirport.name = arrivalAirportName?.content; + } + + if (name) + res.underName.name = name.recursiveContent.replace(/^(.+ .+) \1$/, "$1"); + + if (seat) + res.airplaneSeat = seat.content.split(' ').pop(); + + reservations.push(res); + } + + const [price] = html.eval("//td[normalize-space(text())='Payment Amount']/following-sibling::td[@class='receipt-al-amount']"); + + if (price) + ExtractorEngine.extractPrice(price.recursiveContent + ' USD', reservations); + + return reservations; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/flyfrontier.json new/kitinerary-26.08.2/src/lib/scripts/flyfrontier.json --- old/kitinerary-26.08.1/src/lib/scripts/flyfrontier.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/flyfrontier.json 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "filter": [ + { + "field": "From", + "match": "@t.flyfrontier.com", + "mimeType": "message/rfc822", + "scope": "Ancestors" + } + ], + "function": "main", + "mimeType": "text/html", + "script": "flyfrontier.js" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/sbb.json new/kitinerary-26.08.2/src/lib/scripts/sbb.json --- old/kitinerary-26.08.1/src/lib/scripts/sbb.json 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/scripts/sbb.json 2026-10-06 05:08:07.000000000 +0200 @@ -8,7 +8,7 @@ "scope": "Ancestors" }, { - "match": "\\x043342\\x12\\x05..001", + "match": "\\x043342\\x12\\x05..00\\d", "mimeType": "application/octet-stream", "scope": "Current" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/snalltaget.js new/kitinerary-26.08.2/src/lib/scripts/snalltaget.js --- old/kitinerary-26.08.1/src/lib/scripts/snalltaget.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/snalltaget.js 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Volker Krause <[email protected]> +// SPDX-License-Identifier: LGPL-2.0-or-later + +function extractPdf(pdf, node, barcode) { + let res = JsonLd.newTrainReservation(); + res.reservedTicket.ticketToken = 'qrcode:' + barcode.content; + res.reservedTicket.ticketNumber = barcode.content; + + const text = pdf.pages[barcode.location].text; + res.underName.name = text.match(/.*\n(\S.*\S)\n/)[1]; + res.reservationNumber = text.match(/Booking number: *([A-Z0-9]+)\n/)[1]; + const leg = text.match(/.* (\d\d \S+ \d{4})\n *(\d\d:\d\d) (\S.*)\n *(\d\d:\d\d) (\S.*)\n/); + res.reservationFor.departureTime = JsonLd.toDateTime(leg[1] + leg[2], "dd MMMM yyyyHH:mm", "en"); + res.reservationFor.departureStation.name = leg[3]; + res.reservationFor.arrivalTime = JsonLd.toDateTime(leg[1] + leg[4], "dd MMMM yyyyHH:mm", "en"); + res.reservationFor.arrivalStation.name = leg[5]; + const seat = text.match(/Train (.*) +Car (.*) +Seat (.*?)(?:\n| )/); + res.reservationFor.trainNumber = seat[1]; + res.reservedTicket.ticketedSeat.seatSection = seat[2]; + res.reservedTicket.ticketedSeat.seatNumber = seat[3]; + console.log(seat); + return res; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/snalltaget.json new/kitinerary-26.08.2/src/lib/scripts/snalltaget.json --- old/kitinerary-26.08.1/src/lib/scripts/snalltaget.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-26.08.2/src/lib/scripts/snalltaget.json 2026-10-06 05:08:07.000000000 +0200 @@ -0,0 +1,12 @@ +{ + "filter": [ + { + "match": "^[A-Z0-9]{6}-\\d{5}$", + "mimeType": "text/plain", + "scope": "Descendants" + } + ], + "function": "extractPdf", + "mimeType": "application/pdf", + "script": "snalltaget.js" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/sncf.js new/kitinerary-26.08.2/src/lib/scripts/sncf.js --- old/kitinerary-26.08.1/src/lib/scripts/sncf.js 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/scripts/sncf.js 2026-10-06 05:08:07.000000000 +0200 @@ -495,7 +495,7 @@ res.reservationFor.trainNumber = text.match(/N°\s*(\S+)/)[1]; - var seat = text.match(/Voiture\s*(\S+)\s*Place\s*(\S+)/); + const seat = text.match(/Voiture(?: \(Coach\))?\s*(\S+)\s*Place(?: \(Seat\))?\s*(\S+)/); res.reservedTicket.ticketedSeat.seatSection = seat[1]; res.reservedTicket.ticketedSeat.seatNumber = seat[2]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-26.08.1/src/lib/scripts/sncf.json new/kitinerary-26.08.2/src/lib/scripts/sncf.json --- old/kitinerary-26.08.1/src/lib/scripts/sncf.json 2026-09-03 17:38:25.000000000 +0200 +++ new/kitinerary-26.08.2/src/lib/scripts/sncf.json 2026-10-06 05:08:07.000000000 +0200 @@ -94,7 +94,7 @@ "scope": "Ancestors" }, { - "match": "^0[A-Za-z0-9=+\\/]{172}$", + "match": "^0[A-Za-z0-9=+\\/]{172,192}$", "mimeType": "text/plain", "scope": "Descendants" }
