Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kitinerary for openSUSE:Factory 
checked in at 2025-07-06 17:01:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kitinerary (Old)
 and      /work/SRC/openSUSE:Factory/.kitinerary.new.1903 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kitinerary"

Sun Jul  6 17:01:29 2025 rev:87 rq:1290365 version:25.04.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/kitinerary/kitinerary.changes    2025-06-06 
22:33:37.025371750 +0200
+++ /work/SRC/openSUSE:Factory/.kitinerary.new.1903/kitinerary.changes  
2025-07-06 17:02:48.218265896 +0200
@@ -1,0 +2,10 @@
+Tue Jul  1 15:27:03 UTC 2025 - Christophe Marin <christo...@krop.fr>
+
+- Update to 25.04.3
+  * New bugfix release
+  * For more details please see:
+  * https://kde.org/announcements/gear/25.04.3/
+- Too many changes since 25.04.2, only listing bugfixes:
+  * Add extractor script for ticket-ua railway passes (kde#505891)
+
+-------------------------------------------------------------------

Old:
----
  kitinerary-25.04.2.tar.xz
  kitinerary-25.04.2.tar.xz.sig

New:
----
  kitinerary-25.04.3.tar.xz
  kitinerary-25.04.3.tar.xz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kitinerary.spec ++++++
--- /var/tmp/diff_new_pack.xIKFhC/_old  2025-07-06 17:02:50.162346431 +0200
+++ /var/tmp/diff_new_pack.xIKFhC/_new  2025-07-06 17:02:50.174346928 +0200
@@ -18,11 +18,11 @@
 
 %define kf6_version 6.6.0
 %define qt6_version 6.6.0
-%define kpim6_version 6.4.2
+%define kpim6_version 6.4.3
 
 %bcond_without released
 Name:           kitinerary
-Version:        25.04.2
+Version:        25.04.3
 Release:        0
 Summary:        Data model and extraction system for travel reservations
 License:        LGPL-2.1-or-later


++++++ kitinerary-25.04.2.tar.xz -> kitinerary-25.04.3.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/.flatpak-manifest.json 
new/kitinerary-25.04.3/.flatpak-manifest.json
--- old/kitinerary-25.04.2/.flatpak-manifest.json       2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/.flatpak-manifest.json       2025-06-30 
18:44:49.000000000 +0200
@@ -164,7 +164,7 @@
                 {
                     "type": "git",
                     "url": "https://invent.kde.org/pim/kpkpass";,
-                    "branch": "master"
+                    "branch": "release/25.04"
                 }
             ],
             "cleanup": [
@@ -187,7 +187,7 @@
                 {
                     "type": "git",
                     "url": "https://invent.kde.org/pim/kmime";,
-                    "branch": "master"
+                    "branch": "release/25.04"
                 }
             ],
             "cleanup": [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/CMakeLists.txt 
new/kitinerary-25.04.3/CMakeLists.txt
--- old/kitinerary-25.04.2/CMakeLists.txt       2025-06-02 23:33:53.000000000 
+0200
+++ new/kitinerary-25.04.3/CMakeLists.txt       2025-06-30 18:44:49.000000000 
+0200
@@ -7,10 +7,10 @@
 # KDE Application Version, managed by release script
 set (RELEASE_SERVICE_VERSION_MAJOR "25")
 set (RELEASE_SERVICE_VERSION_MINOR "04")
-set (RELEASE_SERVICE_VERSION_MICRO "2")
+set (RELEASE_SERVICE_VERSION_MICRO "3")
 set (RELEASE_SERVICE_VERSION 
"${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
 
-set(PIM_VERSION "6.4.2")
+set(PIM_VERSION "6.4.3")
 project(KItinerary VERSION ${PIM_VERSION})
 
 set(QT_REQUIRED_VERSION "6.7.0")
@@ -49,8 +49,8 @@
     find_package(SharedMimeInfo 1.3 REQUIRED)
 endif()
 
-set(KMIME_VERSION "6.4.2")
-set(PIM_PKPASS "6.4.2")
+set(KMIME_VERSION "6.4.3")
+set(PIM_PKPASS "6.4.3")
 
 find_package(KPim6Mime ${KMIME_VERSION} CONFIG REQUIRED)
 find_package(KPim6PkPass ${PIM_PKPASS} CONFIG REQUIRED)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitinerary-25.04.2/src/cli/org.kde.kitinerary-extractor.appdata.xml 
new/kitinerary-25.04.3/src/cli/org.kde.kitinerary-extractor.appdata.xml
--- old/kitinerary-25.04.2/src/cli/org.kde.kitinerary-extractor.appdata.xml     
2025-06-02 23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/cli/org.kde.kitinerary-extractor.appdata.xml     
2025-06-30 18:44:49.000000000 +0200
@@ -133,6 +133,7 @@
   </categories>
   <launchable 
type="desktop-id">org.kde.kitinerary-extractor.desktop</launchable>
   <releases>
+    <release version="6.4.3" date="2025-07-03"/>
     <release version="6.4.2" date="2025-06-05"/>
     <release version="6.4.1" date="2025-05-08"/>
     <release version="6.4.0" date="2025-04-17"/>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/jsonlddocument.cpp 
new/kitinerary-25.04.3/src/lib/jsonlddocument.cpp
--- old/kitinerary-25.04.2/src/lib/jsonlddocument.cpp   2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/jsonlddocument.cpp   2025-06-30 
18:44:49.000000000 +0200
@@ -125,7 +125,8 @@
     "yyyy-MM-dd HH:mm:ss",
     "yyyy-MM-dd HH:mm",
     "MM-dd-yyyy HH:mm", // yes, seriously ;(
-    "yyyyMMddTHHmmsst"
+    "yyyyMMddTHHmmsst",
+    "HH:mm"
 };
 static const auto fallbackDateTimePatternCount = 
sizeof(fallbackDateTimePattern) / sizeof(const char *);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/amadeus.js 
new/kitinerary-25.04.3/src/lib/scripts/amadeus.js
--- old/kitinerary-25.04.2/src/lib/scripts/amadeus.js   2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/amadeus.js   2025-06-30 
18:44:49.000000000 +0200
@@ -128,5 +128,35 @@
         res.reservationNumber = rental[11];
         return res;
     }
+    else if (category.match(/Bahn/)) {
+        const resNum = ev.description.match(/Buchungscode: (.*)\n/)[1];
+        const pasName = ev.description.match(/für: (.*)\n/)[1];
+        let reservations = [];
+        let idx = 0;
+        while (true) {
+            let res = JsonLd.newTrainReservation();
+            const leg = ev.description.substr(idx).match(/\n\S.*\S, (\d{1,2} 
\S+ \d{4})\n(.*) - (.*) (\S+ Klasse)\nAbreise von (.*) um (\d\d:\d\d)\nAnkunft 
in (.*) um (\d\d:\d\d)\n(?:.*Wagen. (.*), Sitz. (.*)\n)?/);
+            if (!leg)
+                break;
+            idx += leg.index + leg[0].length - 1;
+            res.reservationFor.provider.name = leg[2];
+            res.reservationFor.trainNumber = leg[3];
+
+            res.reservationFor.departureStation.name = leg[5];
+            res.reservationFor.departureTime = JsonLd.toDateTime(leg[1] + 
leg[6], "dd MMMM yyyyhh:mm", "de");
+
+            res.reservationFor.arrivalStation.name = leg[7];
+            res.reservationFor.arrivalTime = JsonLd.toDateTime(leg[1] + 
leg[8], "dd MMMM yyyyhh:mm", "de");
+
+            res.reservedTicket.ticketedSeat.seatingType = leg[4];
+            res.reservedTicket.ticketedSeat.seatSection = leg[9];
+            res.reservedTicket.ticketedSeat.seatNumber = leg[10];
+
+            res.reservationNumber = resNum;
+            res.underName.name = pasName;
+            reservations.push(res);
+        }
+        return reservations;
+    }
     console.log("unhandled category", category);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/booking.js 
new/kitinerary-25.04.3/src/lib/scripts/booking.js
--- old/kitinerary-25.04.2/src/lib/scripts/booking.js   2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/booking.js   2025-06-30 
18:44:49.000000000 +0200
@@ -8,10 +8,10 @@
 regExMap['en'] = {
     bookingRef: /(?:Booking number|Confirmation:) +([0-9]*)\s+/,
     // 1: adress, 2: city, 3:postal code, 4: country, 5: phone
-    hotelInformation: / *(.+), (.+), (.+), (.+)(?: -|\n)\s+Phone:? (\+[0-9 
]*)\s+/,
-    hotelName: [/(?:\[checkmark\.png\] |\.\d\n)(.*?)(?: is expecting you on|\n 
*\[)/, /\n\n\s*(\S.*\S)\n\n\s*Reservation details\n/],
-    arrivalDate: /Check-in *([A-z]+,? [0-9]{1,2} [A-z]+ [0-9]+|[A-z]+, [A-z]+ 
\d{1,2}, \d{4}) \(f?r?o?m? ?([0-9]{1,2}:[0-9]{2})[^\)]*\)/,
-    departureDate: /Check-out *([A-z]+,? [0-9]{1,2} [A-z]+ [0-9]+|[A-z]+, 
[A-z]+ \d{1,2}, \d{4}) \(.*?([0-9]{1,2}:[0-9]{2})\)/,
+    hotelInformation: / (.+?), ([^\n,]+)(?:, ([^\n,]+))?, ([^\n,]+?)(?: 
-|\n)\s+Phone:? ?(?:[\s]+?)(\+[0-9 ]*)\s+/,
+    hotelName: [/(?:\[checkmark\.png\] |\.\d\n)(.*?)(?: is expecting you on|\n 
*\[)/, /\n\n\s*(?:You'll pay when you stay at )?(\S.*\S)\n\n\s*Reservation 
details\n/],
+    arrivalDate: /Check-in *?\s+? *?([A-z]+,? [0-9]{1,2} [A-z]+ [0-9]+|[A-z]+, 
[A-z]+ \d{1,2}, \d{4}) \(f?r?o?m? ?([0-9]{1,2}:[0-9]{2}(?: [AP]M)?)[^\)]*\)/,
+    departureDate: /Check-out *?\s+? *?([A-z]+,? [0-9]{1,2} [A-z]+ 
[0-9]+|[A-z]+, [A-z]+ \d{1,2}, \d{4}) \(.*?(?:- )?([0-9]{1,2}:[0-9]{2}(?: 
[AP]M)?)\)/,
     person: /Guest name[\n\s]+(.*?)(?:\n| Edit guest name)/
 }
 
@@ -58,6 +58,8 @@
     "dddd, d MMMM yyyy hh:mm",
     "dddd, d. MMMM yyyy hh:mm",
     "dddd, MMMM d, yyyy hh:mm",
+    "dddd, MMMM d, yyyy hh:mm A",
+    "dddd, MMMM d, yyyy h:mm A",
     "dddd, dd 'de' MMMM 'de' yyyy hh:mm",
     "d. MMMM yyyy hh.mm"
 ];
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/dav.js 
new/kitinerary-25.04.3/src/lib/scripts/dav.js
--- old/kitinerary-25.04.2/src/lib/scripts/dav.js       1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/dav.js       2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,20 @@
+/*
+   SPDX-FileCopyrightText: 2025 Stephan Olbrich <stephanolbr...@gmx.de>
+   SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+function parseMembershipCard(pdf, node) {
+    const c = pdf.text.split('\n')
+    const num = c[6].match(/\d{3}\/\d{2}\/(\d+)\*.*$/)[1]
+    let card = {
+        '@type': 'ProgramMembership',
+        programName: 'DAV Mitgliedsausweis (' + c[3].match(/^ *(\S 
Mitglied)$/)[1] + ')',
+        membershipNumber: num,
+        member: {
+            '@type': 'Person',
+            name: c[4],
+        },
+        token: 'barcode128:' + node.childNodes[0].childNodes[0].content,
+    };
+    return card;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/dav.json 
new/kitinerary-25.04.3/src/lib/scripts/dav.json
--- old/kitinerary-25.04.2/src/lib/scripts/dav.json     1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/dav.json     2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,12 @@
+{
+    "filter": [
+        {
+            "match": ".*alpenverein.*",
+            "mimeType": "text/plain",
+            "scope": "Children"
+        }
+    ],
+    "function": "parseMembershipCard",
+    "mimeType": "application/pdf",
+    "script": "dav.js"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/deutschebahn.js 
new/kitinerary-25.04.3/src/lib/scripts/deutschebahn.js
--- old/kitinerary-25.04.2/src/lib/scripts/deutschebahn.js      2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/deutschebahn.js      2025-06-30 
18:44:49.000000000 +0200
@@ -30,7 +30,7 @@
         idx += platform.index + platform[0].length;
         res.reservationFor.departurePlatform = platform[1];
     }
-    var trainId = line.substr(idx).match(compact ? / +([^,]*?)(?=(,|$))/ : / 
+(.*?)(?=(  |$))/);
+    var trainId = line.substr(idx).match(compact ? / +([^,]*?)(?=(,|$))/ : / 
+(.*?)(?=(  |Die |$))/);
     if (trainId) {
         idx += trainId.index + trainId[0].length
         res.reservationFor.trainNumber = trainId[1];
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/deutschebahn.json 
new/kitinerary-25.04.3/src/lib/scripts/deutschebahn.json
--- old/kitinerary-25.04.2/src/lib/scripts/deutschebahn.json    2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/deutschebahn.json    2025-06-30 
18:44:49.000000000 +0200
@@ -9,6 +9,12 @@
             },
             {
                 "field": "issuerId",
+                "match": "6061",
+                "mimeType": "internal/vdv",
+                "scope": "Descendants"
+            },
+            {
+                "field": "issuerId",
                 "match": "6260",
                 "mimeType": "internal/vdv",
                 "scope": "Descendants"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/djh.js 
new/kitinerary-25.04.3/src/lib/scripts/djh.js
--- old/kitinerary-25.04.2/src/lib/scripts/djh.js       2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/djh.js       2025-06-30 
18:44:49.000000000 +0200
@@ -1,5 +1,6 @@
 /*
    SPDX-FileCopyrightText: 2023 Volker Krause <vkra...@kde.org>
+   SPDX-FileCopyrightText: 2025 Carl Schwan <c...@carlschwan.eu>
    SPDX-License-Identifier: LGPL-2.0-or-later
 */
 
@@ -21,3 +22,48 @@
     };
     return card;
 }
+
+function parseReservation(pdf) {
+    const pdfText = pdf.text;
+    const res = JsonLd.newLodgingReservation();
+
+    var num = pdfText.match(/Reservierungs-Nr. (.+)/)[1];
+    res.reservationNumber = num.split(".").join("");
+
+    res.reservationFor.address.addressCountry = "Germany";
+    let nextIsName = false;
+    let i = 0;
+    for (let line of pdf.text.split('\n')) {
+        console.log(i, line)
+        if (i === 0) {
+            res.reservationFor.name = line.trim();
+        }
+        if (i === 2) {
+            res.reservationFor.address.streetAddress = line.trim();
+        }
+        if (i === 3) {
+            res.reservationFor.address.addressLocality = line.trim();
+        }
+        if (nextIsName)  {
+            res.underName.name = line
+            break;
+        }
+        if (line.includes("Telefon: ")) {
+            nextIsName = true;
+        }
+        i++;
+    }
+
+    const info = pdfText.match(/Erste Mahlzeit\n([^ ]+)\s+([^ ]+)\s+/);
+    const checkinTime = pdfText.match(/Check-in: ([^ ]+)\s+/);
+    const checkoutTime = pdfText.match(/Check-out: [^ ]+ - ([^ ]+)/);
+
+    res.checkinTime = JsonLd.toDateTime(info[1] + ' ' + checkinTime[1], 
"dd.MM.yy hh:mm", 'de');
+    res.checkoutTime = JsonLd.toDateTime(info[2] + ' ' + checkoutTime[1], 
"dd.MM.yy hh:mm", 'de');
+
+    const price = pdfText.match(/Gesamtpreis für den oben genannten 
Buchungszeitraum:\s+ (.+) EURO/);
+
+    res.totalPrice = price[1].replace(",", ".");
+
+    return res;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/djh.json 
new/kitinerary-25.04.3/src/lib/scripts/djh.json
--- old/kitinerary-25.04.2/src/lib/scripts/djh.json     2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/djh.json     2025-06-30 
18:44:49.000000000 +0200
@@ -1,7 +1,7 @@
-{
+[{
     "filter": [
         {
-            "match": "^%\\d{3}-\\d{8} 
\\d{3};.*;\\d\\d/\\d{4};\\d\\d\\.\\d\\d\\.\\d{4}\\?$",
+            "match": "^%\\d{3}-\\d{8} 
\\d{3};.*;\\d\\d/\\d{4};\\d\\d\\.\\d\\d\\.\\d{4}\\? *$",
             "mimeType": "text/plain",
             "scope": "Current"
         }
@@ -9,4 +9,10 @@
     "function": "parseMembershipCard",
     "mimeType": "text/plain",
     "script": "djh.js"
-}
+},
+{
+    "filter": [ { "mimeType": "text/plain", "match": "Jugendherberge", 
"scope": "Descendants" } ],
+    "function": "parseReservation",
+    "mimeType": "application/pdf",
+    "script": "djh.js"
+}]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/eurostar.js 
new/kitinerary-25.04.3/src/lib/scripts/eurostar.js
--- old/kitinerary-25.04.2/src/lib/scripts/eurostar.js  2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/eurostar.js  2025-06-30 
18:44:49.000000000 +0200
@@ -51,14 +51,19 @@
         res.reservationNumber = text.match(/PNR: (\S{6})/)[1];
     } else {
         // alternative layout observed in NS-booked Eurostar-branded Thalys 
tickets since 2025
-        trip = text.match(/\S+, (\S+ \d{1,2}, \d{4})  .*\n *FROM  +TO\n 
*(\S.*\S)  +(\S.*\S)\n.*\n *(\d\d:\d\d) +(\d\d:\d\d)/);
-        res.reservationFor.departureTime = JsonLd.toDateTime(trip[1] + ' ' + 
trip[4], "MMM d, yyyy hh:mm", "en");
-        res.reservationFor.arrivalTime = JsonLd.toDateTime(trip[1] + ' ' + 
trip[5], "MMM d, yyyy hh:mm", "en");
-        res.reservationFor.departureStation.name = trip[2];
-        res.reservationFor.arrivalStation.name = trip[3];
+        const date = text.match(/\S+,? (\S+ \d{1,2}, \d{4}|\d{1,2} \S+ \d{4})  
.*\n/);
+        const dep = page.textInRect(0, 0, 0.35, 
1).match(/(?:FROM|DE)\n([\s\S]+)\n *D[EÉ]PART/)[1];
+        const arr = page.textInRect(0.35, 0, 1, 
1).match(/(?:TO|À)\n([\s\S]+)\n *ARRIV/)[1];
+        const time = text.match(/D[EÉ]PART.*\n *(\d\d:\d\d) .*  +(\d\d:\d\d)/);
+        res.reservationFor.departureTime = JsonLd.toDateTime(date[1] + ' ' + 
time[1], ["MMM d, yyyy hh:mm", "d MMMM yyyy hh:mm"], ["en", "fr"]);
+        res.reservationFor.arrivalTime = JsonLd.toDateTime(date[1] + ' ' + 
time[2], ["MMM d, yyyy hh:mm", "d MMMM yyyy hh:mm"], ["en", "fr"]);
+        res.reservationFor.departureStation.name = dep;
+        res.reservationFor.arrivalStation.name = arr;
         const pas = text.match(/PNR\n *(\S.*\S)  +([A-Z0-9]{6})\n/);
         res.reservationNumber = pas[2];
         res.underName.name = pas[1];
+        // pdf417 works just as well, but the vastly different format 
surprises people...
+        res.reservedTicket.ticketToken = 
res.reservedTicket.ticketToken.replace(/^pdf417:/, "azteccode:");
     }
     return res;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/eurostar.json 
new/kitinerary-25.04.3/src/lib/scripts/eurostar.json
--- old/kitinerary-25.04.2/src/lib/scripts/eurostar.json        2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/eurostar.json        2025-06-30 
18:44:49.000000000 +0200
@@ -44,6 +44,12 @@
             "field": "issuerCode",
             "match": "3018",
             "scope": "Descendants"
+        },
+        {
+            "field": "futureUse",
+            "match": "^.0019",
+            "mimeType": "internal/era-elb",
+            "scope": "Descendants"
         }
     ],
     "function": "parsePdfSSB",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/extractors.qrc 
new/kitinerary-25.04.3/src/lib/scripts/extractors.qrc
--- old/kitinerary-25.04.2/src/lib/scripts/extractors.qrc       2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/extractors.qrc       2025-06-30 
18:44:49.000000000 +0200
@@ -85,6 +85,8 @@
         <file>cooltix.js</file>
         <file>czechrailways.json</file>
         <file>czechrailways.js</file>
+        <file>dav.json</file>
+        <file>dav.js</file>
         <file>deutschebahn.json</file>
         <file>deutschebahn.js</file>
         <file>deutschebahn-online-ticket.js</file>
@@ -170,6 +172,8 @@
         <file>hertz.json</file>
         <file>hilton.json</file>
         <file>hilton.js</file>
+        <file>hostelling-scotland.json</file>
+        <file>hostelling-scotland.js</file>
         <file>hotels.com.json</file>
         <file>hotels.com.js</file>
         <file>iberia.json</file>
@@ -202,6 +206,8 @@
         <file>ktel-thesprotias.js</file>
         <file>leoexpress.json</file>
         <file>leoexpress.js</file>
+        <file>leshuttle.json</file>
+        <file>leshuttle.js</file>
         <file>ltg-link.json</file>
         <file>ltg-link.js</file>
         <file>lufthansa.json</file>
@@ -257,6 +263,8 @@
         <file>pretix.js</file>
         <file>qatar-airways.json</file>
         <file>qatar-airways.js</file>
+               <file>reenio.json</file>
+        <file>reenio.js</file>
         <file>regiojet.json</file>
         <file>regiojet.js</file>
         <file>regiondo.json</file>
@@ -321,6 +329,8 @@
         <file>ticketmaster.js</file>
         <file>ticketportal.json</file>
         <file>ticketportal.js</file>
+        <file>tickets-ua.json</file>
+        <file>tickets-ua.js</file>
         <file>tierparkberlin.json</file>
         <file>tierparkberlin.js</file>
         <file>tito.json</file>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitinerary-25.04.2/src/lib/scripts/hostelling-scotland.js 
new/kitinerary-25.04.3/src/lib/scripts/hostelling-scotland.js
--- old/kitinerary-25.04.2/src/lib/scripts/hostelling-scotland.js       
1970-01-01 01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/hostelling-scotland.js       
2025-06-30 18:44:49.000000000 +0200
@@ -0,0 +1,80 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Jonas Junker <jonassimonjun...@proton.me>
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+function main(pdf, node) {
+
+    // create new booking reference
+    var res = JsonLd.newLodgingReservation();
+
+    // extract all text from the pdf
+    const text = pdf.text;
+    // also extract the pageXOffset
+    const page = pdf.pages[0];
+
+    // get the reservation number
+    var bookingRef = text.match(/Your reference number is ([0-9-]+)./)[1];
+    res.reservationNumber = bookingRef;
+
+    // get the checkin time
+    checkinTime = text.match(/Check in:\s+(\S+)\s+to/);
+
+    // get the checkout time
+    checkoutTime = text.match(/Check out:\s\S*\s?[by|latest]\s*(\S+)/)
+
+    // get the arrival and departure dates
+    var regexTable = /(\d{2}\/\d{2}\/\d{4})\s+(\d{1,3})/g;
+
+    // loop through the list of dates of reservations and determine the date of
+    // the checkout
+    const timeformats = ["d/M/yyyy h:mmAP", "d/M/yyyy hAP"]
+    while ((match = regexTable.exec(text)) !== null) {
+
+        // set arrival date for first match
+        if (res.checkinTime === undefined) {
+            res.checkinTime = JsonLd.toDateTime(match[1] + " "  + 
checkinTime[1], timeformats, "en");
+        }
+
+        // checkoutTime is just the date of the current loop plus the numer of 
nights (match[4] of this row
+        res.checkoutTime = JsonLd.toDateTime(match[1] + " " + checkoutTime[1], 
timeformats, "en");
+        res.checkoutTime.setDate(res.checkoutTime.getDate() + 
parseInt(match[2], 10))
+    }
+
+    // name of person reserving
+    var namePerson = text.match(/Dear ([A-Z ]+),/i)[1];
+    res.underName.name = namePerson
+
+    // name and address of hostel
+    var hostel = text.match(/\s+(\w+\s.*)\s+Tel:\s+(\+?44? ?\(\d+\)?\s+[0-9 
]+)\s+Address:\s+([^,]+),\s*([^,]+)(?:,\s*([^,]+))?\s+(\S{2,4} 
\S{3})\s+Email:\s+(\S+@\S+)\s+([0-9 ]+)\s+Directions/)
+
+    // name of hostel
+    res.reservationFor.name = hostel[1]
+
+    // telephone number of hostel. May be split in 2 parts
+    var telephone = hostel[2];
+
+    if (hostel[8] !== undefined) {
+        telephone += " " + hostel[8];
+    }
+    res.reservationFor.telephone = telephone;
+
+    // e-mail adress of hostel
+    res.reservationFor.email = hostel[7];
+
+    // adress of hostel
+    res.reservationFor.address.streetAddress = hostel[3];
+    res.reservationFor.address.addressLocality = hostel[4];
+    if (hostel[5] !== undefined ) {
+            res.reservationFor.address.addressRegion = hostel[5];
+    }
+    res.reservationFor.address.postalCode = hostel[6];
+
+    // geo coordinates of hostel
+    const links = page.linksInRect(0, 0, 1, 1);
+    res.reservationFor.geo = JsonLd.toGeoCoordinates(links[1].url);
+
+    return res
+}
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kitinerary-25.04.2/src/lib/scripts/hostelling-scotland.json 
new/kitinerary-25.04.3/src/lib/scripts/hostelling-scotland.json
--- old/kitinerary-25.04.2/src/lib/scripts/hostelling-scotland.json     
1970-01-01 01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/hostelling-scotland.json     
2025-06-30 18:44:49.000000000 +0200
@@ -0,0 +1,13 @@
+{
+    "filter": [
+        {
+            "field": "From",
+            "match": "^reservati...@hostellingscotland.org.uk$",
+            "mimeType": "message/rfc822",
+            "scope": "Ancestors"
+        }
+    ],
+    "function": "main",
+    "mimeType": "application/pdf",
+    "script": "hostelling-scotland.js"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/leoexpress.js 
new/kitinerary-25.04.3/src/lib/scripts/leoexpress.js
--- old/kitinerary-25.04.2/src/lib/scripts/leoexpress.js        2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/leoexpress.js        2025-06-30 
18:44:49.000000000 +0200
@@ -1,7 +1,28 @@
 // SPDX-FileCopyrightText: 2024 Volker Krause <vkra...@kde.org>
-// SPDX-FileCopyrightText: 2024 David Pilarcik <meow@charliecat.space>
+// SPDX-FileCopyrightText: 2024-2025 David Pilarcik <meow@charliecat.space>
 // SPDX-License-Identifier: LGPL-2.0-or-later
 
+const i18n = {
+       "en": {
+               __lookingFor: "TICKET/TAX RECEIPT",
+               "extracting name - TICKET/TAX RECEIPT": /(.*) +TICKET\/TAX 
RECEIPT/,
+               "(train connection) string": "(train connection)",
+               "Class": "CLASS",
+       },
+       "sk": {
+               __lookingFor: "LÍSTOK/DAŇOVÝ DOKLAD",
+               "extracting name - TICKET/TAX RECEIPT": /(.*) +LÍSTOK\/DAŇOVÝ 
DOKLAD/,
+               "(train connection) string": "(vlakové spojenie)",
+               "Class": "TRIEDA",
+       },
+       "cs": {
+               __lookingFor: "JÍZDENKA/DAŇOVÝ DOKLAD",
+               "extracting name - TICKET/TAX RECEIPT": /(.*) +JÍZDENKA\/DAŇOVÝ 
DOKLAD/,
+               "(train connection) string": "(vlakové spojení)",
+               "Class": "TŘÍDA",
+       }
+}
+
 function extractEvent(ev, node) {
     let res = JsonLd.newTrainReservation();
     res.reservationFor.departureStation.name = ev.location;
@@ -17,21 +38,61 @@
 }
 
 function extractPdf(pdf) {
-    const text = pdf.pages[0].text;
-    const dep = text.match(/(\d\d:\d\d \d{1,2}\.\d{1,2}\.\d{2}) \[(.*?)\] +/);
-    const leg = text.match(/ +([^\s\d\[\]].*\S) -> (\S.*\S) 
\((\d{1,2}\.\d{1,2}\.\d{2} \d\d:\d\d)/);
-    let res = JsonLd.newTrainReservation();
-    res.reservationFor.departureTime = JsonLd.toDateTime(dep[1], "hh:mm 
d.M.yy", "sk");
-    res.reservationFor.trainNumber = dep[2];
-    res.reservationFor.departureStation.name = leg[1];
-    res.reservationFor.arrivalStation.name = leg[2];
-    res.reservationFor.arrivalTime = JsonLd.toDateTime(leg[3], "d.M.yy hh:mm", 
"sk");
-
-    const hdr = text.match(/^(\S.*\S)  +(\d{5}-\d{5}-\d{5})/);
-    res.underName.name = hdr[1];
-    res.reservationNumber = hdr[2].replace(/-/g, '');
-    res.reservedTicket.ticketToken = 'qrcode:' + res.reservationNumber;
-    return res;
+    const text = pdf.text;
+       const parts = text.split(/(?=\n([A-Ž]*\s)+ +\d{5}-\d{5}-\d{5})/g)
+       const result = [];
+
+       let lang = i18n["en"];
+       for (let l in i18n) {
+               if (text.includes(i18n[l].__lookingFor)) {
+                       lang = i18n[l];
+                       break;
+               }
+       }
+
+       for (let part of parts) {
+               if (!part.includes("->")) continue;
+
+               const dep = part.match(/(\d\d:\d\d \d{1,2}\.\d{1,2}\.\d{2}) 
\[(.*?)\] +/);
+               const leg = part.match(/ +([^\s\d\[\]].*\S) -> (\S.*\S) 
\((\d{1,2}\.\d{1,2}\.\d{2} \d\d:\d\d)/);
+
+               let res = (part.includes(lang["(train connection) string"])) ? 
JsonLd.newTrainReservation() : JsonLd.newBusReservation();
+
+               if (part.includes(lang["(train connection) string"])) {
+                       res.reservationFor.trainNumber = dep[2];
+                       res.reservationFor.departureStation.name = leg[1];
+                       res.reservationFor.arrivalStation.name = leg[2];
+               } else {
+                       res.reservationFor.departureBusStop.name = leg[1];
+                       res.reservationFor.arrivalBusStop.name = leg[2];
+                       res.reservationFor.busNumber = dep[2];
+               }
+
+               res.reservationFor.departureTime = JsonLd.toDateTime(dep[1], 
"hh:mm d.M.yy", "sk");
+               res.reservationFor.arrivalTime = JsonLd.toDateTime(leg[3], 
"d.M.yy hh:mm", "sk");
+               const hdr = part.match(/((?:[A-Ž]*\s){2,}) 
+(\d{5}-\d{5}-\d{5})/);
+               res.underName.name = hdr[1];
+               res.underName.email = /^(.*@.* ) +/gm.exec(part)?.[1].trim() || 
undefined;
+               res.reservationNumber = hdr[2].replace(/-/g, '');
+               res.reservedTicket.ticketToken = 'qrcode:' + 
res.reservationNumber;
+               res.reservedTicket.name = lang["extracting name - TICKET/TAX 
RECEIPT"].exec(part)[1];
+               res.reservedTicket.ticketedSeat.seatNumber = 
part.match(/\s+([A-Z]\d{2,3})/)?.[1] || undefined;
+
+               // This thing is done due to the length of the Class Cell being 
short for some Class Names
+               let partLines = part.split("\n");
+               let classHeaderLine = partLines.findIndex(line => 
line.includes(lang["Class"]));
+               let classHeaderIndex = 
partLines[classHeaderLine].indexOf(lang["Class"]);
+               res.reservedTicket.ticketedSeat.seatingType = ""
+               for (let i = classHeaderLine + 1; i < (classHeaderLine + 4); 
i++) {
+                       let className = 
partLines[i].substring(classHeaderIndex, classHeaderIndex + 15).trim() || " ";
+                       console.log(partLines[i].substring(classHeaderIndex, 
classHeaderIndex + 15))
+                       if (className) 
res.reservedTicket.ticketedSeat.seatingType += className;
+               }
+
+               ExtractorEngine.extractPrice(part, res);
+               result.push(res);
+       }
+    return result;
 }
 
 function extractPkpass(pass, node) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/leshuttle.js 
new/kitinerary-25.04.3/src/lib/scripts/leshuttle.js
--- old/kitinerary-25.04.2/src/lib/scripts/leshuttle.js 1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/leshuttle.js 2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: ⓒ 2025 Volker Krause <vkra...@kde.org>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+function extractConfirmation(text) {
+    let res = JsonLd.newTrainReservation();
+    const trip = text.match(/\S+ (\d{1,2} \S+ \d{4})\n[\S\s]+?Departs (.*) at 
(\d\d:\d\d)[\s\S]+?Arrives in (.*) at (\d\d:\d\d)\n/)
+    res.reservationFor.departureStation.name = trip[2];
+    res.reservationFor.departureTime = JsonLd.toDateTime(trip[1] + trip[3], 
"dd MMMM yyyyhh:mm", "en");
+    res.reservationFor.arrivalStation.name = trip[4];
+    res.reservationFor.arrivalTime = JsonLd.toDateTime(trip[1] + trip[5], "dd 
MMMM yyyyhh:mm", "en");
+    res.reservationNumber = text.match("Booking Reference:\n(.*)\n")[1];
+    ExtractorEngine.extractPrice(text.match(/Total 
paid.*[\s\n]+\d.*\n/)[0].replace(/[\s\n]+/g, ' '), res);
+    return res;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/leshuttle.json 
new/kitinerary-25.04.3/src/lib/scripts/leshuttle.json
--- old/kitinerary-25.04.2/src/lib/scripts/leshuttle.json       1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/leshuttle.json       2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,13 @@
+{
+    "filter": [
+        {
+            "field": "From",
+            "match": "@email.leshuttle.com",
+            "mimeType": "message/rfc822",
+            "scope": "Ancestors"
+        }
+    ],
+    "function": "extractConfirmation",
+    "mimeType": "text/plain",
+    "script": "leshuttle.js"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/luma.js 
new/kitinerary-25.04.3/src/lib/scripts/luma.js
--- old/kitinerary-25.04.2/src/lib/scripts/luma.js      2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/luma.js      2025-06-30 
18:44:49.000000000 +0200
@@ -1,12 +1,15 @@
 // SPDX-FileCopyrightText: 2024 David Pilarčík <meow@charliecat.space>
 // SPDX-License-Identifier: LGPL-2.0-or-later
 
-function parseEvent(event) {
-    const res = JsonLd.newEventReservation();
+function parseEvent(event, node) {
+    const res = Object.assign(JsonLd.newEventReservation(), node.result[0]);
 
     // https://lu.ma/check-in/evt-***************?pk=g-*************** // 
Ticket Token
     // https://lu.ma/       e/evt-***************?pk=g-*************** // Link 
in Description
-    res.reservedTicket.ticketToken = 'qrCode:' + 
event.description.match(/(https:\/\/lu.ma\/e\/evt-[A-z0-9]*\?pk=g-[A-z0-9]*)/gm)[0].replace('/e/',
 '/check-in/')
+       let eventUrl = 
event.description.match(/(https:\/\/lu.ma\/e\/evt-\S*\?pk=g-\S*)/gm)[0]
+    res.reservedTicket.ticketToken = 'qrCode:' + eventUrl.replace('/e/', 
'/check-in/')
+       res.reservationNumber = new 
URL(eventUrl).searchParams.get('pk').replace('g-', '') || undefined; // 
Extracting defacto reservation code from the URL
+       res.reservationFor.url = eventUrl;
 
     res.reservationFor.name = event.summary;
     res.reservationFor.startDate = JsonLd.readQDateTime(event, 'dtStart');
@@ -15,7 +18,7 @@
     res.reservationFor.location.name = 
event.description.match(/\sAddress:\s(.*)\s/)[1]
     res.reservationFor.location.geo.latitude = event.geoLatitude
     res.reservationFor.location.geo.longitude = event.geoLongitude
-
+       
     res.underName.name = event.attendees[0].name;
     res.underName.email = event.attendees[0].email;
 
@@ -25,6 +28,9 @@
 function parsePkPass(pass, node) {
     const res = node.result[0]
 
+       res.reservationFor.url = 
pass.field["event_url"].value.match(/(https?:\/\/[^\s]+)"/)[1];
+       res.reservationNumber = (new 
URL(res.reservedTicket.ticketToken.replace('qrCode:', 
''))).searchParams.get('pk').replace('g-', ''); // Extracting defacto 
reservation code from the URL
+
     res.underName = {
         "@type": "Person",
         email: pass.field["guest_email"].value,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/mav.js 
new/kitinerary-25.04.3/src/lib/scripts/mav.js
--- old/kitinerary-25.04.2/src/lib/scripts/mav.js       2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/mav.js       2025-06-30 
18:44:49.000000000 +0200
@@ -70,16 +70,15 @@
 }
 
 function parseTicket(pdf, node, triggerNode) {
-    var reservations = new Array();
+    let reservations = [];
     const text = pdf.pages[triggerNode.location].text;
-    var idx = 0;
+    let idx = 0;
     while (true) {
-        var trip = text.substr(idx).match(/(\d{2,4}\.\d{2}\.\d{2,4})\. 
*(\d{2}:\d{2}) *(.*) *-> *(.*) *(\d{2}:\d{2}) *(.*) *(\d)\./);
-        if (!trip) {
+        const trip = text.substr(idx).match(/(\d{2,4}\.\d{2}\.\d{2,4})\. 
*(\d{2}:\d{2}) *(.*) *-> *(.*) *(\d{2}:\d{2}) *(.*) *(\d)\./);
+        if (!trip)
             break;
-        }
-        idx += trip.index + trip[0].length
-        var res = JsonLd.clone(triggerNode.result[0]);
+        idx += trip.index + trip[0].length;
+        let res = JsonLd.clone(triggerNode.result[0]);
         res.reservationFor.departureStation.name = trip[3];
         res.reservationFor.arrivalStation.name = trip[4];
         res.reservationFor.departureTime = JsonLd.toDateTime(trip[1] + 
trip[2], ["yyyy.MM.ddhh:mm", "dd.MM.yyyyhh:mm"], "hu");
@@ -87,6 +86,26 @@
         res.reservationFor.trainNumber = trip[6];
         reservations.push(res);
     }
+    if (reservations.length > 0)
+        return reservations;
+
+    // new/alternative layout
+    idx = 0;
+    while (true) {
+        const trip = text.substr(idx).match(/  +(\S.*\S)  +(\d\d:\d\d)(?:  
+(\S+))?\n(\d{2,4}\.\d{2}\.\d{2,4})\.  +(?:(\S+)  +)?(\d)\.\n  +(\S.*\S)  
+(\d\d:\d\d)/);
+        if (!trip)
+            break;
+        idx += trip.index + trip[0].length;
+        let res = JsonLd.clone(triggerNode.result[0]);
+        res.reservationFor.departureStation.name = trip[1];
+        res.reservationFor.departureTime = JsonLd.toDateTime(trip[4] + 
trip[2], ["yyyy.MM.ddhh:mm", "dd.MM.yyyyhh:mm"], "hu");
+        res.reservationFor.trainNumber = trip[3] ?? trip[5];
+        res.reservedTicket.ticketedSeat.seatingType = trip[6];
+        res.reservationFor.arrivalStation.name = trip[7];
+        res.reservationFor.arrivalTime = JsonLd.toDateTime(trip[4] + trip[8], 
["yyyy.MM.ddhh:mm", "dd.MM.yyyyhh:mm"], "hu");
+        reservations.push(res);
+    }
+
     return reservations;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/ouigo-es.js 
new/kitinerary-25.04.3/src/lib/scripts/ouigo-es.js
--- old/kitinerary-25.04.2/src/lib/scripts/ouigo-es.js  2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/ouigo-es.js  2025-06-30 
18:44:49.000000000 +0200
@@ -35,14 +35,24 @@
         res.reservationFor.arrivalStation.name = stations[2];
         res.reservationFor.arrivalTime = JsonLd.toDateTime(stations[3], 
"hh:mm", "es");
     } else {
-        const stationsV2 = 
topLeft.match(/\n\d\d:\d\d\n(.*)\n(\d\d:\d\d)\n(.*)/);
+        const stationsV2 = topLeft.match(/\n* \d\d:\d\d\n(.*)\n 
*(\d\d:\d\d)\n(.*)/);
         res.reservationFor.departureStation.name = stationsV2[1];
         res.reservationFor.arrivalStation.name = stationsV2[3];
         res.reservationFor.arrivalTime = JsonLd.toDateTime(stationsV2[2], 
"hh:mm", "es");
     }
 
     const topRight = page.textInRect(0.5, 0.0, 1.0, 0.5);
-    res.underName.name = topRight.match(/^(.*)\n/)[1];
-    res.reservationNumber = topRight.match(/  +([A-Z0-9]{6})\n/)[1];
+    const name = page.text.match(/^ *Localizador: .*\n(.*)\n/);
+    if (name)
+        res.underName.name = name[1];
+    else
+        res.underName.name = topRight.match(/^(.*)\n/)[1];
+    res.reservationNumber = topRight.match(/[: ] +([A-Z0-9]{6})\n/)[1];
+
+    const price = page.text.match(/Precio.*/);
+    if (price)
+        ExtractorEngine.extractPrice(price[0], res);
+    else
+        ExtractorEngine.extractPrice(page.text, res);
     return res;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/reenio.js 
new/kitinerary-25.04.3/src/lib/scripts/reenio.js
--- old/kitinerary-25.04.2/src/lib/scripts/reenio.js    1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/reenio.js    2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: 2025 David Pilarčík <meow@charliecat.space>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+const i18n = {
+       "sk-SK": {
+               __lookingFor: "Kód slúži na jednoznačnú identifikáciu",
+               "New reservation created for": /Vytvorenie novej 
rezervácie\s(.*)/gm,
+               "which is under name": /ktorá je na meno (.*)./gm,
+               "date and time": /Termín\s.* (\d+.\d+.\d+) (\d+:\d+) – 
(\d+:\d+)/gm,
+               "where?": /KDE\?\s(.*)\s(.*)/gm
+       }
+}
+
+function parseMail(html) {
+       const res = JsonLd.newEventReservation();
+       const text = html.root.recursiveContent
+
+       let lang = i18n["sk-SK"]
+
+       for (let langMutation in i18n) {
+               if (text.includes(i18n[langMutation].__lookingFor)) {
+                       lang = i18n[langMutation]
+                       break
+               }
+       }
+
+       res.reservationFor.name = lang["New reservation created 
for"].exec(text)[1];
+
+       let reservationCode = 
/([A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{3})/gm.exec(text)[0];
+       res.reservationNumber = reservationCode;
+       res.reservedTicket.ticketToken = "qrCode:" + reservationCode
+
+       let userName = lang["which is under name"].exec(text)[1];
+       res.underName = {
+               '@type': 'Person',
+               name: String(userName ?? "").trim()
+       };
+
+       // example "Termín pondelok 19.5.2025 18:00 – 20:00"
+       // d.m.yyyy HH:mm – HH:mm
+       let [_d, date, startTime, endTime] = lang["date and time"].exec(text);
+       res.reservationFor.startDate = JsonLd.toDateTime(date + ' ' + 
startTime, "dd.M.yyyy HH:mm", "sk");
+       res.reservationFor.endDate = JsonLd.toDateTime(date + ' ' + endTime, 
"dd.M.yyyy HH:mm", "sk");
+
+       // example "KDE?  Stará tržnica, Bratislava  Námestie SNP 25, 
Bratislava 811 01, Slovensko"
+       // [ _a, Venue Name, Venue Address ] // Address can be asumed that is 
in Slovak or Czech
+       let [_a, venueName, venueAddress] = lang["where?"].exec(text)
+       res.reservationFor.location.name = venueName
+       let [ streetAddress, municipalityAndPostal, Country ] = 
venueAddress.split(", ")
+       res.reservationFor.location.address.streetAddress = streetAddress
+       res.reservationFor.location.address.postalCode = 
municipalityAndPostal.match(/\d+ \d+/)[0] // Asuming numaric postal code
+       res.reservationFor.location.address.addressLocality = 
municipalityAndPostal.replace(/\d+ \d+/, "").trim()
+       // res.reservationFor.location.address.addressCountry = Country.trim() 
|| undefined // Address receved is in the language of the user, hard to parse
+       res.reservationFor.location.geo = 
JsonLd.toGeoCoordinates(/href="(https:\/\/maps.google.com.*)"/.exec(html.rawData)[1]);
+
+       res.provider = { '@type': 'Organization', name: 
text.split("\n").slice(-3, -2)[0] };
+
+       let webAccessToken = /\/.{13}\/\?accessToken=.{40}/gm.exec(text)[0]
+       let reenioDomain = /\/\/reenio..*\/s.*\/r/gm.exec(text)[0]
+       res.potentialAction = [
+               { "@type": "UpdateAction", "target": 
`https:${reenioDomain}/ReservationDetail${webAccessToken}` },
+               { "@type": "CancelAction", "target": 
`https:${reenioDomain}/CancelReservation${webAccessToken}` },
+               { "@type": "DownloadAction", "target": 
`https:${reenioDomain}/DownloadReservationCodePdf${webAccessToken}` },
+       ]
+       
+       return res
+}
+
+function extractPass(pass, node) {
+       let res = node.result[0];
+
+       res.reservationNumber = res.reservedTicket.ticketToken.split(":")[1]
+       res.reservationFor.startDate = pass.field["startDate"].value
+
+       // in first sample the name is "Stará tržnica, Bratislava"
+       // in the second is "Uvedenie knihy Rozumne rastúce mesto, Stará 
tržnica, Bratislava"
+       // this tries to split the name and the location
+       let splitName = pass.field["TermName"].value.split(", ")
+       res.reservationFor.location = JsonLd.newObject("Place")
+       res.reservationFor.location.address = JsonLd.newObject("PostalAddress")
+       if (splitName.length == 2) {
+               res.reservationFor.location.name = splitName[0]
+               res.reservationFor.location.address.addressLocality = 
splitName[1]
+       } else if (splitName.length >= 3) {
+               res.reservationFor.name = splitName.slice(0, -2).join(", ")
+               res.reservationFor.location.name = splitName.slice(-2)[0]
+               res.reservationFor.location.address.addressLocality = 
splitName.slice(-2)[1]
+       }
+
+       res.potentialAction = [
+               { "@type": "UpdateAction", "target": 
pass.field["detailUrl"].value },
+               { "@type": "CancelAction", "target": 
pass.field["detailUrl"].value.replace("ReservationDetail", "CancelReservation") 
},
+               { "@type": "DownloadAction", "target": 
pass.field["detailUrl"].value.replace("ReservationDetail", 
"DownloadReservationCodePdf") },
+       ]
+
+       return res
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/reenio.json 
new/kitinerary-25.04.3/src/lib/scripts/reenio.json
--- old/kitinerary-25.04.2/src/lib/scripts/reenio.json  1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/reenio.json  2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,28 @@
+[
+       {
+               "filter": [
+                       {
+                               "field": "From",
+                               "match": "@reenio.info",
+                               "mimeType": "message/rfc822",
+                               "scope": "Ancestors"
+                       }
+               ],
+               "function": "parseMail",
+               "mimeType": "text/html",
+               "script": "reenio.js"
+       },
+       {
+        "filter": [
+            {
+                "field": "passTypeIdentifier",
+                "match": "pass.cz.reenio.ticket",
+                "mimeType": "application/vnd.apple.pkpass",
+                "scope": "Current"
+            }
+        ],
+        "function": "extractPass",
+        "mimeType": "application/vnd.apple.pkpass",
+        "script": "reenio.js"
+    }
+]
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/sncb.json 
new/kitinerary-25.04.3/src/lib/scripts/sncb.json
--- old/kitinerary-25.04.2/src/lib/scripts/sncb.json    2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/sncb.json    2025-06-30 
18:44:49.000000000 +0200
@@ -34,7 +34,7 @@
         "filter": [
             {
                 "field": "From",
-                "match": "@b-europe.com|b-rail.be",
+                "match": "@b-europe.com|b-rail.be|belgiantrain.be",
                 "mimeType": "message/rfc822",
                 "scope": "Parent"
             }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/sncf.js 
new/kitinerary-25.04.3/src/lib/scripts/sncf.js
--- old/kitinerary-25.04.2/src/lib/scripts/sncf.js      2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/sncf.js      2025-06-30 
18:44:49.000000000 +0200
@@ -242,28 +242,32 @@
 
 function parseSecutixPdfItineraryV2(text, res)
 {
-    var reservations = new Array();
-    var pos = 0;
+    let reservations = [];
+    let pos = 0;
     while (true) {
-        var data = text.substr(pos).match(/ *(\d+h\d+)\n *(.*)\n *(.*)\n(?: 
*Voiture (\d+) - Place (\d+)\n.*\n.*\n)?(?: *Opéré par .*\n)? 
*(\d+h\d+)\n(.*)\n/);
-        if (!data)
+        const trip = text.substr(pos).match(/ 
*(\d\dh\d\d)\n(.*)\n(.*)\n([\S\s]*?) *(\d\dh\d\d)\n(.*)\n/);
+        if (!trip)
             break;
-        pos += data.index + data[0].length;
+        pos += trip.index + trip[0].length;
 
-        var leg = JsonLd.newTrainReservation();
-        leg.reservationFor.departureStation.name = data[2];
+        let leg = JsonLd.newTrainReservation();
+        leg.reservationFor.departureStation.name = trip[2];
         leg.reservationFor.departureDay = res.reservationFor.departureDay;
-        leg.reservationFor.departureTime = JsonLd.toDateTime(data[1], 
"hh'h'mm", "fr");
-        leg.reservationFor.arrivalStation.name = data[7];
-        leg.reservationFor.arrivalTime = JsonLd.toDateTime(data[6], "hh'h'mm", 
"fr");
-        leg.reservationFor.trainNumber = data[3];
+        leg.reservationFor.departureTime = JsonLd.toDateTime(trip[1], 
"hh'h'mm", "fr");
+        leg.reservationFor.arrivalStation.name = trip[6];
+        leg.reservationFor.arrivalTime = JsonLd.toDateTime(trip[5], "hh'h'mm", 
"fr");
+        leg.reservationFor.trainNumber = trip[3];
         leg.underName = res.underName;
         leg.reservationNumber = res.reservationNumber;
         leg.reservedTicket = res.reservedTicket;
-        leg.reservedTicket.ticketedSeat.seatSection = data[4];
-        leg.reservedTicket.ticketedSeat.seatNumber = data[5];
         leg.programMembershipUsed = res.programMembershipUsed;
 
+        const seat = trip[4].match(/Voiture (\d+) - Place (\d+)\n/);
+        if (seat) {
+            leg.reservedTicket.ticketedSeat.seatSection = seat[1];
+            leg.reservedTicket.ticketedSeat.seatNumber = seat[2];
+        }
+
         reservations.push(leg);
     }
     return reservations;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/tickets-ua.js 
new/kitinerary-25.04.3/src/lib/scripts/tickets-ua.js
--- old/kitinerary-25.04.2/src/lib/scripts/tickets-ua.js        1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/tickets-ua.js        2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,36 @@
+/*
+   SPDX-FileCopyrightText: ⓒ 2025 Volker Krause <vkra...@kde.org>
+   SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+function extractTrainPass(pass, node) {
+    let res = JsonLd.newTrainReservation();
+    res.reservedTicket = node.result[0].reservedTicket;
+    res.reservationFor.provider = node.result[0].reservationFor.provider;
+    res.underName = JsonLd.newObject("Person");
+    res.underName.name = pass.field["header_0"].value;
+    res.reservationNumber = pass.field["secondary_0"].value;
+    res.reservationFor.departureStation.name = pass.field['primary_0'].label;
+    res.reservationFor.departureDay = pass.field['auxiliary_0'].value;
+    res.reservationFor.departureTime = pass.field['primary_0'].value;
+    res.reservationFor.arrivalStation.name = pass.field['primary_1'].label;
+    res.reservationFor.arrivalTime = pass.field['primary_1'].value;
+    res.reservationFor.trainNumber = pass.field["auxiliary_1"].value;
+
+    const stationCodes = res.reservedTicket.ticketToken.match(/\n\((\d{7})\) 
\S+\n\((\d{7})\) [^ ]+\n/);
+    if (stationCodes) {
+        res.reservationFor.departureStation.identifier = 'uic:' + 
stationCodes[1];
+        res.reservationFor.arrivalStation.identifier = 'uic:' + 
stationCodes[2];
+    }
+
+    const seat = pass.field['auxiliary_2'].value.match(/^(\S+)\/(\S+)$/);
+    if (seat) {
+        res.reservedTicket.ticketedSeat = {
+            '@type': 'Seat',
+            seatSection: seat[1],
+            seatNumber: seat[2]
+        };
+    }
+
+    return res;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/tickets-ua.json 
new/kitinerary-25.04.3/src/lib/scripts/tickets-ua.json
--- old/kitinerary-25.04.2/src/lib/scripts/tickets-ua.json      1970-01-01 
01:00:00.000000000 +0100
+++ new/kitinerary-25.04.3/src/lib/scripts/tickets-ua.json      2025-06-30 
18:44:49.000000000 +0200
@@ -0,0 +1,13 @@
+{
+    "filter": [
+        {
+            "field": "passTypeIdentifier",
+            "match": "pass.ua.tickets.passbook.TicketsRail",
+            "mimeType": "application/vnd.apple.pkpass",
+            "scope": "Current"
+        }
+    ],
+    "function": "extractTrainPass",
+    "mimeType": "application/vnd.apple.pkpass",
+    "script": "tickets-ua.js"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kitinerary-25.04.2/src/lib/scripts/tito.js 
new/kitinerary-25.04.3/src/lib/scripts/tito.js
--- old/kitinerary-25.04.2/src/lib/scripts/tito.js      2025-06-02 
23:33:53.000000000 +0200
+++ new/kitinerary-25.04.3/src/lib/scripts/tito.js      2025-06-30 
18:44:49.000000000 +0200
@@ -6,8 +6,8 @@
         res.reservationFor.startDate = JsonLd.toDateTime(dt[1] + dt[2], "MMMM 
ddyyyy", "en");
     } else if (dt = date.match(/(\S+) (\d{1,2})\S{2}\S(\d{1,2})\S{2}, 
(\d{4})/)) {
         console.log(dt);
-        res.reservationFor.startDate = JsonLd.toDateTime(dt[1] + ' ' + dt[2] + 
dt[4], "MMMM ddyyyy", "en");
-        res.reservationFor.endDate = JsonLd.toDateTime(dt[1] + ' ' + dt[3] + 
dt[4], "MMMM ddyyyy", "en");
+        res.reservationFor.startDate = JsonLd.toDateTime(dt[1] + ' ' + dt[2] + 
' ' + dt[4], "MMMM d yyyy", "en");
+        res.reservationFor.endDate = JsonLd.toDateTime(dt[1] + ' ' + dt[3] + ' 
' + dt[4], "MMMM d yyyy", "en");
     }
 }
 
@@ -38,12 +38,12 @@
 function extractPdf(pdf, node, barcode) {
     const page = pdf.pages[barcode.location];
     const topRight = page.textInRect(0.5, 0.0, 1.0, 0.5);
-    const ev = topRight.match(/([\S\s]+)\n(.*, \d{4})\n([\S\s]+)/);
+    const ev = topRight.match(/([\S\s]+)\n(.*, 
\d{4})\n(?:\s*\d{1,2}:\d{2}[ap]m.*\n)?([\S\s]+)/);
 
     let res = JsonLd.newEventReservation();
     res.reservationFor.name = ev[1];
     parseDate(res, ev[2]);
-    res.reservationFor.location.name = ev[3];
+    res.reservationFor.location.name = ev[3].match(/(.*)(?:By the power 
of)?/)[1];
     res.reservedTicket.ticketToken = 'qrCode:' + barcode.content;
 
     const left = page.textInRect(0.0, 0.0, 0.5, 1.0);

Reply via email to