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);