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"
             }

Reply via email to