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-06-06 22:33:26 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kitinerary (Old) and /work/SRC/openSUSE:Factory/.kitinerary.new.19631 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kitinerary" Fri Jun 6 22:33:26 2025 rev:86 rq:1283172 version:25.04.2 Changes: -------- --- /work/SRC/openSUSE:Factory/kitinerary/kitinerary.changes 2025-05-09 18:44:24.419661066 +0200 +++ /work/SRC/openSUSE:Factory/.kitinerary.new.19631/kitinerary.changes 2025-06-06 22:33:37.025371750 +0200 @@ -1,0 +2,23 @@ +Tue Jun 3 21:12:27 UTC 2025 - Christophe Marin <christo...@krop.fr> + +- Update to 25.04.2 + * New bugfix release + * For more details please see: + * https://kde.org/announcements/gear/25.04.2/ +- Changes since 25.04.1: + * Fix Eurostar ticket extraction for French language tickets + * Handle checkout time ranges in German booking.com emails + * Add booking.com extractor for PDF confirmations + * Add copyright/licensing information + * Add Stena Line extractor + * Add Viking Line extractor + * Add IHG extractor + * Fix Tallink extractor for English language + * Don't require an arrival time for boat reservations + * Improve Flixbus PDF ticket extractor + * Support extracting an alternate Flixbus pkpass variant + * Extend pkpass preprocessing for bus tickets + * Update static extractor build documentation + * Give published static extractor builds the release service version + +------------------------------------------------------------------- Old: ---- kitinerary-25.04.1.tar.xz kitinerary-25.04.1.tar.xz.sig New: ---- kitinerary-25.04.2.tar.xz kitinerary-25.04.2.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kitinerary.spec ++++++ --- /var/tmp/diff_new_pack.QBN0wm/_old 2025-06-06 22:33:37.501391380 +0200 +++ /var/tmp/diff_new_pack.QBN0wm/_new 2025-06-06 22:33:37.505391545 +0200 @@ -18,11 +18,11 @@ %define kf6_version 6.6.0 %define qt6_version 6.6.0 -%define kpim6_version 6.4.1 +%define kpim6_version 6.4.2 %bcond_without released Name: kitinerary -Version: 25.04.1 +Version: 25.04.2 Release: 0 Summary: Data model and extraction system for travel reservations License: LGPL-2.1-or-later ++++++ kitinerary-25.04.1.tar.xz -> kitinerary-25.04.2.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/CMakeLists.txt new/kitinerary-25.04.2/CMakeLists.txt --- old/kitinerary-25.04.1/CMakeLists.txt 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/CMakeLists.txt 2025-06-02 23:33:53.000000000 +0200 @@ -3,7 +3,14 @@ # SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16 FATAL_ERROR) -set(PIM_VERSION "6.4.1") + +# 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 "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") + +set(PIM_VERSION "6.4.2") project(KItinerary VERSION ${PIM_VERSION}) set(QT_REQUIRED_VERSION "6.7.0") @@ -42,8 +49,8 @@ find_package(SharedMimeInfo 1.3 REQUIRED) endif() -set(KMIME_VERSION "6.4.1") -set(PIM_PKPASS "6.4.1") +set(KMIME_VERSION "6.4.2") +set(PIM_PKPASS "6.4.2") find_package(KPim6Mime ${KMIME_VERSION} CONFIG REQUIRED) find_package(KPim6PkPass ${PIM_PKPASS} CONFIG REQUIRED) @@ -143,3 +150,5 @@ ki18n_install(po) +# for static extractor build packaging +file(WRITE ${CMAKE_BINARY_DIR}/version.txt ${RELEASE_SERVICE_VERSION}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/po/es/kitinerary6.po new/kitinerary-25.04.2/po/es/kitinerary6.po --- old/kitinerary-25.04.1/po/es/kitinerary6.po 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/po/es/kitinerary6.po 2025-06-02 23:33:53.000000000 +0200 @@ -1,14 +1,15 @@ -# Copyright (C) YEAR This_file_is_part_of_KDE -# This file is distributed under the same license as the PACKAGE package. +# Spanish translations for kitinerary6.po package. +# Copyright (C) 2018-2025 This file is copyright: +# This file is distributed under the same license as the kitinerary package. # -# Javier Vinal <fjvi...@gmail.com>, 2018, 2019. -# Eloy Cuadra <ecua...@eloihr.net>, 2020, 2021, 2022. +# SPDX-FileCopyrightText: 2018, 2019 Javier Vinal <fjvi...@gmail.com> +# SPDX-FileCopyrightText: 2020, 2021, 2022, 2025 Eloy Cuadra <ecua...@eloihr.net> msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: kitinerary6\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" "POT-Creation-Date: 2024-01-15 00:38+0000\n" -"PO-Revision-Date: 2022-07-21 18:21+0200\n" +"PO-Revision-Date: 2025-05-18 12:55+0100\n" "Last-Translator: Eloy Cuadra <ecua...@eloihr.net>\n" "Language-Team: Spanish <kde-l10n...@kde.org>\n" "Language: es\n" @@ -185,53 +186,3 @@ "Referencia de la reserva: %1\n" "Bajo el nombre: %2\n" "Lugar de recogida: %3" - -#~ msgid "Reservation reference: %1" -#~ msgstr "Referencia de la reserva: %1" - -#~| msgid "" -#~| "Reservation reference: %1\n" -#~| "Under name: %2\n" -#~| "\n" -#~| "PickUp location: %3\n" -#~| "\n" -#~| "Dropoff Location: %4" -#~ msgid "" -#~ "Reservation reference: %1\n" -#~ "Under name: %2\n" -#~ "\n" -#~ "Pickup location: %3\n" -#~ "\n" -#~ "Dropoff location: %4" -#~ msgstr "" -#~ "Referencia de la reserva: %1\n" -#~ "Bajo el nombre: %2\n" -#~ "\n" -#~ "Lugar de recogida: %3\n" -#~ "\n" -#~ "Lugar de entrega: %4" - -#~ msgid "Rent car reservation: %1" -#~ msgstr "Reserva de coche de alquiler: %1" - -#~ msgid "" -#~ "Check-in: %1\n" -#~ "Check-out: %2\n" -#~ "Booking reference: %3" -#~ msgstr "" -#~ "Entrada: %1\n" -#~ "Salida: %2\n" -#~ "Referencia de la reserva: %3" - -#~ msgid "" -#~ "Number Of People: %1\n" -#~ "Reservation reference: %2\n" -#~ "Under name: %3" -#~ msgstr "" -#~ "Número de personas: %1\n" -#~ "Referencia de la reserva: %2\n" -#~ "A nombre de: %3" - -#~ msgctxt "<street>, <postal code> <city>, <country>" -#~ msgid "%1, %2 %3, %4" -#~ msgstr "%1, %2 %3, %4" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/po/pl/kitinerary6.po new/kitinerary-25.04.2/po/pl/kitinerary6.po --- old/kitinerary-25.04.1/po/pl/kitinerary6.po 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/po/pl/kitinerary6.po 2025-06-02 23:33:53.000000000 +0200 @@ -16,7 +16,6 @@ "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Lokalize 23.08.5\n" #: calendarhandler.cpp:171 #, kde-format diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/scripts/README.md new/kitinerary-25.04.2/scripts/README.md --- old/kitinerary-25.04.1/scripts/README.md 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/scripts/README.md 2025-06-02 23:33:53.000000000 +0200 @@ -10,18 +10,20 @@ ## Deployment -The extractor needs external data in the form of translation catalogs and iso-codes files. Those are provided +When using the iCal output format, the extractor needs external translation catalogs. Those are provided in the same archive as the binary, and need to be placed in the same relative location to the binary to be found. Alternatively, they can be placed in a path listed in the `XDG_DATA_DIRS` environment variable. -## Translations - -When using the iCal output format, translations are relevant. This is done using Gettext and thus follows the +Translations are done using Gettext and thus follows the environment variables that influence that (`LANG`, `LANGUAGE`, `LC_ALL`, etc). This also implies that Glibc locale data has to be installed on the system, Gettext will not work without those. +## CD builds + +CD builds are published to https://cdn.kde.org/ci-builds/pim/kitinerary/. + ## Local builds If you want to locally reproduce the same build, this can be done by running the commands from the build scripts in the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/scripts/package.sh new/kitinerary-25.04.2/scripts/package.sh --- old/kitinerary-25.04.1/scripts/package.sh 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/scripts/package.sh 2025-06-02 23:33:53.000000000 +0200 @@ -27,7 +27,7 @@ # determing version # VERSION=$CI_COMMIT_REF_SLUG -VERSION=`cat $BUILD_ROOT/$CI_PROJECT_PATH/CMakeLists.txt | grep "set(PIM_VERSION" | sed -e 's/")//' | sed -e 's/.*"//'` +VERSION=`cat $BUILD_ROOT/$CI_PROJECT_PATH/build/version.txt` # prepare output for publishing PACKAGE_ROOT=$CI_PROJECT_DIR/kde-ci-packages/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/cli/org.kde.kitinerary-extractor.appdata.xml new/kitinerary-25.04.2/src/cli/org.kde.kitinerary-extractor.appdata.xml --- old/kitinerary-25.04.1/src/cli/org.kde.kitinerary-extractor.appdata.xml 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/cli/org.kde.kitinerary-extractor.appdata.xml 2025-06-02 23:33:53.000000000 +0200 @@ -133,6 +133,7 @@ </categories> <launchable type="desktop-id">org.kde.kitinerary-extractor.desktop</launchable> <releases> + <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"/> <release version="6.3.3" date="2025-03-06"/> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/extractorvalidator.cpp new/kitinerary-25.04.2/src/lib/extractorvalidator.cpp --- old/kitinerary-25.04.1/src/lib/extractorvalidator.cpp 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/extractorvalidator.cpp 2025-06-02 23:33:53.000000000 +0200 @@ -110,8 +110,7 @@ { return filterPlace(trip.departureBoatTerminal()) && filterPlace(trip.arrivalBoatTerminal()) - && trip.departureTime().isValid() - && trip.arrivalTime().isValid(); + && trip.departureTime().isValid(); } bool ExtractorValidatorPrivate::filterEvent(const Event &event) const diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/processors/pkpassdocumentprocessor.cpp new/kitinerary-25.04.2/src/lib/processors/pkpassdocumentprocessor.cpp --- old/kitinerary-25.04.1/src/lib/processors/pkpassdocumentprocessor.cpp 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/processors/pkpassdocumentprocessor.cpp 2025-06-02 23:33:53.000000000 +0200 @@ -6,6 +6,7 @@ #include "pkpassdocumentprocessor.h" +#include <KItinerary/BusTrip> #include <KItinerary/DocumentUtil> #include <KItinerary/Event> #include <KItinerary/ExtractorDocumentNodeFactory> @@ -316,6 +317,38 @@ return res; } +[[nodiscard]] static BusReservation extractBusTicket(KPkPass::Pass *pass, BusReservation res) +{ + auto trip = res.reservationFor().value<BusTrip>(); + + // "relevantDate" is the best guess for the departure time if we didn't find an explicit field for it + if (pass->relevantDate().isValid() && !trip.departureTime().isValid()) { + // TODO try to recover timezone? + trip.setDepartureTime(pass->relevantDate()); + } + + // location is the best guess for the departure station geo coordinates + auto depStation = trip.departureBusStop(); + auto depGeo = depStation.geo(); + if (pass->locations().size() == 1 && !depGeo.isValid()) { + const auto loc = pass->locations().at(0); + depGeo.setLatitude(loc.latitude()); + depGeo.setLongitude(loc.longitude()); + depStation.setGeo(depGeo); + trip.setDepartureBusStop(depStation); + } + + // organizationName is the best guess for the provider + auto provider = trip.provider(); + if (provider.name().isEmpty()) { + provider.setName(pass->organizationName()); + trip.setProvider(provider); + } + + res.setReservationFor(trip); + return res; +} + static void extractEventTicketPass(KPkPass::Pass *pass, EventReservation &eventRes) { auto event = eventRes.reservationFor().value<Event>(); @@ -480,6 +513,14 @@ res = trainRes; break; } + case KPkPass::BoardingPass::Bus: + { + auto busRes = res.value<BusReservation>(); + busRes = extractBusTicket(pass, busRes); + busRes.setUnderName(extractPerson(pass, busRes.underName().value<Person>())); + res = busRes; + break; + } default: if (!node.result().isEmpty()) { // don't overwrite better results from child nodes return; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/booking.js new/kitinerary-25.04.2/src/lib/scripts/booking.js --- old/kitinerary-25.04.1/src/lib/scripts/booking.js 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/scripts/booking.js 2025-06-02 23:33:53.000000000 +0200 @@ -28,10 +28,10 @@ regExMap['de'] = { bookingRef: /(?:Buchungsnummer|Bestätigungsnummer): ([0-9]*)\s+/, // 1: hotel name, 2: adress, 3: city, 4:postal code, 5: country, 6: phone - hotelInformation: /(?:Lage )?(.+), (.+), (.+), (.+) ?-?\s+Telefon:? (\+[0-9 \-]+)\n/, - hotelName: [/\[checkmark.png\] Die Unterkunft (.*)\s+erwartet Sie/, /\n\n\s*(\S.*\S)\n\n\s*\[\S/], + hotelInformation: /(?:Lage )?(.+?), ([^\n,]+)(?:, ([^\n,]+))?, ([^\n,]+?) ?-?\s+Telefon:? (\+[0-9 \-]+)\n/, + hotelName: [/Die Unterkunft (.*)\s+erwartet Sie/, /\n\n\s*(\S.*\S)\n\n\s*\[\S/], arrivalDate: /Anreise ([A-Z][a-z]+, [0-9]{1,2}\. \S+ [0-9]{4}) \((?:ab )?([0-9]{1,2}:[0-9]{2}).*\)/, - departureDate: /Abreise ([A-Z][a-z]+, [0-9]{1,2}\. \S+ [0-9]{4}) \(bis ([0-9]{1,2}:[0-9]{2})\)/, + departureDate: /Abreise ([A-Z][a-z]+, [0-9]{1,2}\. \S+ [0-9]{4}) \(.*?([0-9]{1,2}:[0-9]{2})\)/, person: /Name des Gastes[\n\s]+(.*?)(?:\n| Name des Gastes bearbeiten)/, } @@ -85,9 +85,9 @@ const hotel = text.match(regExMap[locale]['hotelInformation']); res.reservationFor.address.streetAddress = hotel[1]; - res.reservationFor.address.postalCode = hotel[3]; + res.reservationFor.address.postalCode = hotel[4] ? hotel[3] : ""; res.reservationFor.address.addressLocality = hotel[2]; - res.reservationFor.address.addressCountry = hotel[4]; + res.reservationFor.address.addressCountry = hotel[4] ? hotel[4] : hotel[3]; res.reservationFor.telephone = hotel[5]; const arrivalDate = text.match(regExMap[locale]['arrivalDate']); @@ -201,3 +201,44 @@ res.reservationFor.address.addressCountry = addrElems[addrElems.length - 1]; return parseHtmlCommon(doc, node, res); } + +function parsePdf(pdf, node) +{ + var pdfText = pdf.text; + + var res = JsonLd.newLodgingReservation(); + + var num = pdfText.match(/CONFIRMATION NUMBER: (.+)/)[1]; + res.reservationNumber = num.split(".").join(""); + + var info = pdfText.match(/CHECK-OUT\s+ROOMS\s+NIGHTS\n\s+(.+)\n(.+)\n\s+(\d+)\s+(\d+)\s+\d+\s*\/\d+\n\s+Address:\s+(.+)\s+([A-Z]+)\s+([A-Z]+)\n\s+(\d+)\s+(.+)\n\s+.*\n\s+Phone: (.+)\n\s+from (\d\d:\d\d)\s+until (\d\d:\d\d)\n\s+GPS coordinates: (.+)/); + + res.reservationFor.name = info[1] + info[2]; + res.reservationFor.address.streetAddress = info[5]; + res.reservationFor.address.postalCode = info[8]; + res.reservationFor.address.addressLocality = info[9]; + res.reservationFor.telephone = info[10]; + + var coords = parseCoordinates(info[13]); + res.reservationFor.geo.latitude = coords[0]; + res.reservationFor.geo.longitude = coords[1]; + + var checkin = info[3] + " " + info[6] + " " + info[11]; + var checkout = info[4] + " " + info[7] + " " + info[12]; + res.checkinTime = JsonLd.toDateTime(checkin, "d MMMM hh:mm", 'en'); + res.checkoutTime = JsonLd.toDateTime(checkout, "d MMMM hh:mm", 'en'); + + return res; +} + +function parseCoordinates(input) { + const regex = /([NS])\s*(\d{1,3})[°º]?\s*(\d+(?:\.\d+)?)[,\s]+([EW])\s*(\d{1,3})[°º]?\s*(\d+(?:\.\d+)?)/i; + const match = input.match(regex); + + const [, latHem, latDeg, latMin, lonHem, lonDeg, lonMin] = match; + + const latitude = (parseInt(latDeg, 10) + parseFloat(latMin) / 60) * (latHem.toUpperCase() === 'S' ? -1 : 1); + const longitude = (parseInt(lonDeg, 10) + parseFloat(lonMin) / 60) * (lonHem.toUpperCase() === 'W' ? -1 : 1); + + return [latitude, longitude]; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/booking.json new/kitinerary-25.04.2/src/lib/scripts/booking.json --- old/kitinerary-25.04.1/src/lib/scripts/booking.json 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/scripts/booking.json 2025-06-02 23:33:53.000000000 +0200 @@ -15,5 +15,11 @@ "filter": [ { "field": "From", "match": "@booking.com", "mimeType": "message/rfc822", "scope": "Ancestors" } ], "script": "booking.js", "function": "parseHtmlAlternative" + }, + { + "mimeType": "application/pdf", + "filter": [ { "mimeType": "text/plain", "match": "This print version of your confirmation contains the most important information about your booking. It can be used to check in", "scope": "Descendants" } ], + "script": "booking.js", + "function": "parsePdf" } ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/flixbus.js new/kitinerary-25.04.2/src/lib/scripts/flixbus.js --- old/kitinerary-25.04.1/src/lib/scripts/flixbus.js 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/scripts/flixbus.js 2025-06-02 23:33:53.000000000 +0200 @@ -44,7 +44,7 @@ const idx = addr1.lastIndexOf(','); place.address.streetAddress = addr1.substring(0, idx); place.address.addressLocality = addr1.substr(idx + 1); - place.geo = JsonLd.toGeoCoordinates(links.shift().url); + place.geo = JsonLd.toGeoCoordinates(links.shift()?.url); } function parsePdfTicket(pdf, node, triggerNode) @@ -63,7 +63,7 @@ let reservations = []; while (true) { const times = timeColumn.substr(idxTime).match(/(\d\d:\d\d)\n([^:]*?\n)?([^:]*?\n)?(\d\d:\d\d)/); - const stations = stationColumn.substr(idxStations).match(/(.*)\n[ ]+(.*)(?:\n|,\n +(.*)\n)(?:.*(?:NOTE:|Wagennummerierung|platform|The regional part).(?:.*\n)+)?.*(?:Bus|Autobus|Zug|Strecke|Route|Tratta) +(.*)\n.*(?:Direction|à destination de|Kierunek|richting|Richtung|Direzione) (.*)\n(?:.*(?:Operated|Betrieben|Uitgevoerd|Effettuata).*\n)?(.*)\n(?:[ ]+(.*?)(?:\n|,\n +(.*)\n))?/); + const stations = stationColumn.substr(idxStations).match(/\b([^].*)\n[ ]+(.*)(?:\n|,\n +(.*)\n)(?:.*(?:NOTE:|Wagennummerierung|platform|The regional part|Die Bussteignummer).(?:.*\n)+?)?.*(?:Bus|Autobus|Zug|Strecke|Route|Tratta)\s+(\S.+)\n.*(?:Direction|à destination de|Kierunek|richting|Richtung|Direzione) (.*)\n(?:.*(?:Operated|Betrieben|Uitgevoerd|Effettuata).*\n)?(.*)\n(?:[ ]+(.*?)(?:\n|,\n +(.*)\n))?/); if (!times || !stations) { break; } @@ -130,4 +130,27 @@ //TODO: Passangier List, Seating return res -} \ No newline at end of file +} + +function parsePkPass2(pass, node) { + let res = Object.assign(JsonLd.newBusReservation(), node.result[0]); + + res.reservationFor.departureBusStop.name = pass.primaryFields[0].label; + res.reservationFor.departureTime = pass.primaryFields[0].value + res.reservationFor.arrivalBusStop = { + '@type': 'BusStation', + name: pass.primaryFields[1].label + }; + res.reservationFor.arrivalTime = pass.primaryFields[1].value + + res.reservationFor.busNumber = pass.field["transport-type"].value.split(" ")[0] + res.reservationFor.busName = pass.field["transport-type"].value + + res.reservationNumber = pass.field["booking-number"].value + res.underName.name = pass.field["passenger"].value; + res.reservedTicket.ticketedSeat = { + '@type': 'Seat', + seatNumber: pass.field["seat"].value + }; + return res +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/flixbus.json new/kitinerary-25.04.2/src/lib/scripts/flixbus.json --- old/kitinerary-25.04.1/src/lib/scripts/flixbus.json 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/scripts/flixbus.json 2025-06-02 23:33:53.000000000 +0200 @@ -35,5 +35,18 @@ ], "script": "flixbus.js", "function": "parsePkPass" + }, + { + "mimeType": "application/vnd.apple.pkpass", + "filter": [ + { + "field": "passTypeIdentifier", + "match": "pass.de.flixbus", + "mimeType": "application/vnd.apple.pkpass", + "scope": "Current" + } + ], + "script": "flixbus.js", + "function": "parsePkPass2" } ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/ihg.js new/kitinerary-25.04.2/src/lib/scripts/ihg.js --- old/kitinerary-25.04.1/src/lib/scripts/ihg.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-25.04.2/src/lib/scripts/ihg.js 2025-06-02 23:33:53.000000000 +0200 @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2025 Johannes Krattenmacher <git.nore...@krateng.ch> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +function parseConfirmation(html) { + + var res = JsonLd.newLodgingReservation(); + + const content = html.root.recursiveContent; + + const formats = ["d MMM yyyy h a"]; + + // Supporting English and German + let info = content.match(/(?:Confirmation|Buchungsnummer):?\s*([0-9#]+)\n(.+)\n[\s\S]*?(.+)\n(?:Address|Adresse):\n([\s\S]*?)\n(?:Front Desk|Rezeption):\n(\d{4,25})\n(?:Email|E-Mail):\n(.*)\n(?:Dates|Daten)\n(\d{1,2} \w{3} \d{4})[\s\S]*?(\d{1,2} \w{3} \d{4})\nCheck in (\d{1,2} [ap]m).* \/ Check out[\s\S]*?(\d{1,2} [ap]m)[\s\S]*?(?:Reservation|Reservierung)/) + + // sometimes includes #, sometimes not - not sure if hotel specific + res.reservationNumber = info[1]; + res.reservationFor.name = info[2] + info[3]; + res.reservationFor.address.streetAddress = info[4]; + res.reservationFor.telephone = info[5]; + res.reservationFor.email = info[6]; + + let checkin = info[7] + " " + info[9]; + let checkout = info[8] + " " + info[10]; + + res.checkinTime = JsonLd.toDateTime(checkin, formats, 'en'); + res.checkoutTime = JsonLd.toDateTime(checkout, formats, 'en'); + + return res; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/ihg.json new/kitinerary-25.04.2/src/lib/scripts/ihg.json --- old/kitinerary-25.04.1/src/lib/scripts/ihg.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-25.04.2/src/lib/scripts/ihg.json 2025-06-02 23:33:53.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "filter": [ + { + "field": "From", + "match": "@tx.ihg.com", + "mimeType": "message/rfc822", + "scope": "Ancestors" + } + ], + "function": "parseConfirmation", + "mimeType": "text/html", + "script": "ihg.js" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/stena.js new/kitinerary-25.04.2/src/lib/scripts/stena.js --- old/kitinerary-25.04.1/src/lib/scripts/stena.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-25.04.2/src/lib/scripts/stena.js 2025-06-02 23:33:53.000000000 +0200 @@ -0,0 +1,65 @@ +/* + SPDX-FileCopyrightText: 2025 Johannes Krattenmacher <git.nore...@krateng.ch> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +function parseConfirmation(html) { + const content = html.root.recursiveContent; + let res = JsonLd.newBoatReservation(); + res.reservationNumber = content.match(/BOOKING REFERENCE:.*\n.*?(\d+)/)[1]; + + var timeinfo = content.match(/Departs\n.*(\d\d\/\d\d\/\d\d\d\d).*\n.*(\d\d:\d\d)\n.*Arrives\n.*(\d\d\/\d\d\/\d\d\d\d).*\n.*(\d\d:\d\d)/); + + res.reservationFor.departureTime = JsonLd.toDateTime(timeinfo[1] + " " + timeinfo[2], "dd/MM/yyyy hh:mm", "en"); + res.reservationFor.arrivalTime = JsonLd.toDateTime(timeinfo[3] + " " + timeinfo[4], "dd/MM/yyyy hh:mm", "en"); + + var routeinfo = content.match(/Route Information\n(.+)-(.+)/); + var departPort = routeinfo[1]; + var arrivePort = routeinfo[2]; + + // hardcode ports that Stena Line uses? + // all but liepaja and travemunde are NOT confirmed to use this exact string on the ticket + var ports = { + 'Liepaja': [56.529230, 20.998445, "14A Brīvostas Street", 3405, "Liepāja", "Stena Line Liepāja Ferry Terminal"], + 'Travemünde': [53.940075, 10.852651, "Zum Hafenplatz 1", 23570, "Lübeck-Travemünde", "Skandinavienkai"], + 'Frederikshavn': [57.43459, 10.54365, "Færgehavnsvej 10", 9900, "Frederikshavn", "Stena Line Denmark A/S"], + 'Gothenburg': [57.701195, 11.947163, "Emigrantvägen 20", 41327, "Gothenburg", "Denmark Terminal"], + 'Hook of Holland': [51.974122, 4.128671, "Stationsweg 10", 3151, "Hook of Holland", "Stena Line BV"], + 'Harwich': [51.946494, 1.252731, "Parkeston Quay", "CO12 4SR", "Harwich", "Harwich International Port"], + 'Ventspils': [57.398297, 21.569751, "Dārzu iela 6", 3601, "Ventspils", "Pramju Terminalis"], + 'Nynäshamn': [58.93915, 17.97527, "Norvikvägen 26", 14945, "Nynäshamn", "Norviks färjeterminal"], + 'Rostock': [54.141775, 12.104687, "Zum Fährterminal 1", 18147, "Rostock", "Fährterminal 1"], + 'Trelleborg': [55.373026, 13.154049, "Norra Nyhamnsgatan 1A", 23161, "Trelleborg", "Norra Nyhamnsgatan 1A"], + 'Gdynia': [54.533212, 18.544250, "ul. Polska 4", 81339, "Gdynia", "Gdynia Port"], + 'Karlskrona': [56.1646, 15.6308, "Verkövägen 101", 37165, "Lyckeby, Karlskrona", "Stena Line Scandinavia AB"], + // more + } + + if (departPort in ports) { + res.reservationFor.departureBoatTerminal.geo.latitude = ports[departPort][0]; + res.reservationFor.departureBoatTerminal.geo.longitude = ports[departPort][1]; + res.reservationFor.departureBoatTerminal.address.streetAddress = ports[departPort][2]; + res.reservationFor.departureBoatTerminal.address.postalCode = ports[departPort][3]; + res.reservationFor.departureBoatTerminal.address.addressLocality = ports[departPort][4]; + res.reservationFor.departureBoatTerminal.name = ports[departPort][5]; + } + else { + res.reservationFor.departureBoatTerminal.name = departPort; + res.reservationFor.departureBoatTerminal.address.addressLocality = departPort; + } + + if (arrivePort in ports) { + res.reservationFor.arrivalBoatTerminal.geo.latitude = ports[arrivePort][0]; + res.reservationFor.arrivalBoatTerminal.geo.longitude = ports[arrivePort][1]; + res.reservationFor.arrivalBoatTerminal.address.streetAddress = ports[arrivePort][2]; + res.reservationFor.arrivalBoatTerminal.address.postalCode = ports[arrivePort][3]; + res.reservationFor.arrivalBoatTerminal.address.addressLocality = ports[arrivePort][4]; + res.reservationFor.arrivalBoatTerminal.name = ports[arrivePort][5]; + } + else { + res.reservationFor.arrivalBoatTerminal.name = arrivePort; + res.reservationFor.arrivalBoatTerminal.address.addressLocality = arrivePort; + } + + return res; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/stena.json new/kitinerary-25.04.2/src/lib/scripts/stena.json --- old/kitinerary-25.04.1/src/lib/scripts/stena.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-25.04.2/src/lib/scripts/stena.json 2025-06-02 23:33:53.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "filter": [ + { + "field": "From", + "match": "@stenaline.com", + "mimeType": "message/rfc822", + "scope": "Ancestors" + } + ], + "function": "parseConfirmation", + "mimeType": "text/html", + "script": "stena.js" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/tallink.js new/kitinerary-25.04.2/src/lib/scripts/tallink.js --- old/kitinerary-25.04.1/src/lib/scripts/tallink.js 2025-05-03 00:09:58.000000000 +0200 +++ new/kitinerary-25.04.2/src/lib/scripts/tallink.js 2025-06-02 23:33:53.000000000 +0200 @@ -7,17 +7,19 @@ let res = JsonLd.newBoatReservation(); res.reservationNumber = triggerNode.content.match(/;(\d+)-/)[1]; const text = pdf.pages[triggerNode.location].textInRect(0.0, 0.0, 0.68, 1.0); - const dep = text.match(/Abfahrt Von (\S.*?) +\S.*(\d\d\.\d\d.\d{4}) +(.*)\n.*(\d\d:\d\d) +(\S.*)/); + const dep = text.match(/(?:Abfahrt Von|Departure from) (\S.*?) +\S.*(\d\d\.\d\d.\d{4}) +(.*)\n.*(\d\d:\d\d) +(\S.*)/); res.reservationFor.departureBoatTerminal.name = dep[3]; res.reservationFor.departureBoatTerminal.address.addressLocality = dep[1]; res.reservationFor.departureBoatTerminal.address.streetAddress = dep[5]; res.reservationFor.departureTime = JsonLd.toDateTime(dep[2] + ' ' + dep[4], 'dd.MM.yyyy hh:mm', 'de'); - const arr = text.match(/Ankunft in (\S.*?) +\S.*(\d\d\.\d\d.\d{4}) +(.*)\n.*(\d\d:\d\d) +(\S.*)/); + const arr = text.match(/(?:Ankunft in|Arrives in) (\S.*?) +\S.*(\d\d\.\d\d.\d{4}) +(.*)\n.*(\d\d:\d\d) +(\S.*)/); res.reservationFor.arrivalBoatTerminal.name = arr[3]; res.reservationFor.arrivalBoatTerminal.address.addressLocality = arr[1]; res.reservationFor.arrivalBoatTerminal.address.streetAddress = arr[5]; res.reservationFor.arrivalTime = JsonLd.toDateTime(arr[2] + ' ' + arr[4], 'dd.MM.yyyy hh:mm', 'de'); + // TODO security code? is there any field for that? + return res; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/vikingline.js new/kitinerary-25.04.2/src/lib/scripts/vikingline.js --- old/kitinerary-25.04.1/src/lib/scripts/vikingline.js 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-25.04.2/src/lib/scripts/vikingline.js 2025-06-02 23:33:53.000000000 +0200 @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: 2025 Johannes Krattenmacher <git.nore...@krateng.ch> + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +function parsePdf(pdf, node) { + var res = JsonLd.newBoatReservation(); + var pdfText = pdf.text; + res.reservationNumber = pdfText.match(/Booking number (\d{5,10})/)[1]; + + // use the more specific info for departure + var info = pdfText.match(/Port information and check-in times\n(.*):(.*)\nAddress\n(.*), (\d+) (.*)\nGPS: N\/lat\s*(\d{1,3})°\s*(\d{1,2}\.\d+)',\s*E\/lon\s*(\d{1,3})°\s*(\d{1,2}\.\d+)'/); + // viking lines is always N E + + res.reservationFor.departureBoatTerminal.name = info[2]; + res.reservationFor.departureBoatTerminal.address.streetAddress = info[3]; + res.reservationFor.departureBoatTerminal.address.addressLocality = info[5]; + res.reservationFor.departureBoatTerminal.address.postalCode = info[4]; + res.reservationFor.departureBoatTerminal.geo.latitude = parseInt(info[6], 10) + (parseFloat(info[7])/60); + res.reservationFor.departureBoatTerminal.geo.longitude = parseInt(info[8], 10) + (parseFloat(info[9])/60); + + var shortinfo = pdfText.match(/Departure[^\S\r\n]*(.*) (.{3}) (\d{1,2}) (.+) (\d{4}) at (\d{1,2}):(\d{2})\nArrival[^\S\r\n]*(.*) (.{3}) (\d{1,2}) (.+) (\d{4}) at (\d{1,2}):(\d{2})/); + var depart = shortinfo[3] + " " + shortinfo[4] + " " + shortinfo[5] + " " + shortinfo[6] + ":" + shortinfo[7]; + var arrive = shortinfo[10] + " " + shortinfo[11] + " " + shortinfo[12] + " " + shortinfo[13] + ":" + shortinfo[14]; + + res.reservationFor.arrivalBoatTerminal.address.addressLocality = shortinfo[8]; + // terminal name needed for validation? this just uses the city for now + res.reservationFor.arrivalBoatTerminal.name = shortinfo[8]; + res.reservationFor.departureTime = JsonLd.toDateTime(depart, 'd MMMM yyyy HH:mm', 'en'); + res.reservationFor.arrivalTime = JsonLd.toDateTime(arrive, 'd MMMM yyyy HH:mm', 'en'); + + return res; +} + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-25.04.1/src/lib/scripts/vikingline.json new/kitinerary-25.04.2/src/lib/scripts/vikingline.json --- old/kitinerary-25.04.1/src/lib/scripts/vikingline.json 1970-01-01 01:00:00.000000000 +0100 +++ new/kitinerary-25.04.2/src/lib/scripts/vikingline.json 2025-06-02 23:33:53.000000000 +0200 @@ -0,0 +1,8 @@ +{ + "filter": [ + { "mimeType": "text/plain", "match": "sales.vikingline.com", "scope": "Descendants" } + ], + "function": "parsePdf", + "mimeType": "application/pdf", + "script": "vikingline.js" +}