From b8f48ade1d42957535207a971879d58642574d29 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Wed, 8 Jan 2025 20:26:44 +0100 Subject: Do not use DB HAFAS by default. Use DB IRIS instead. --- lib/Travelynx/Command/database.pm | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'lib/Travelynx/Command/database.pm') diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index a7d13a8..6ba80a3 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -2689,6 +2689,18 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;} } ); }, + + # v58 -> v59 + # DB HAFAS is dead. Default to DB IRIS for now. + sub { + my ($db) = @_; + $db->query( + qq{ + alter table users alter column backend_id set default 0; + update schema_version set version = 59; + } + ); + }, ); sub sync_stations { -- cgit v1.2.3 From a9b5a18943c3e2070703e745cd1131a02fd20365 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Sun, 23 Mar 2025 18:07:50 +0100 Subject: Preliminary DBRIS support (not user-accessible yet) working: * checkin * checkout * realtime data * polylines * carriage formation (long-distance only) to do: * geolocation * redirects after checkout / undo * traewelling sync * use dbris by default --- lib/Travelynx.pm | 170 +++++++++++++++++++++++++++++++++- lib/Travelynx/Command/database.pm | 153 ++++++++++++++++++++++++++++++ lib/Travelynx/Command/work.pm | 105 +++++++++++++++++++++ lib/Travelynx/Controller/Account.pm | 19 ++-- lib/Travelynx/Controller/Traveling.pm | 103 +++++++++++++++++--- lib/Travelynx/Helper/DBRIS.pm | 138 +++++++++++++++++++++++++++ lib/Travelynx/Model/InTransit.pm | 132 +++++++++++++++++++++++++- lib/Travelynx/Model/Journeys.pm | 11 ++- lib/Travelynx/Model/Stations.pm | 82 ++++++++++++---- lib/Travelynx/Model/Users.pm | 9 +- public/static/js/travelynx-actions.js | 4 + templates/_departures_dbris.html.ep | 54 +++++++++++ templates/departures.html.ep | 18 ++-- templates/landingpage.html.ep | 1 + 14 files changed, 945 insertions(+), 54 deletions(-) create mode 100644 lib/Travelynx/Helper/DBRIS.pm create mode 100644 templates/_departures_dbris.html.ep (limited to 'lib/Travelynx/Command/database.pm') diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 2b7fdf5..98ba4aa 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -21,6 +21,7 @@ use List::UtilsBy qw(uniq_by); use List::MoreUtils qw(first_index); use Travel::Status::DE::DBRIS::Formation; use Travelynx::Helper::DBDB; +use Travelynx::Helper::DBRIS; use Travelynx::Helper::HAFAS; use Travelynx::Helper::IRIS; use Travelynx::Helper::Sendmail; @@ -216,6 +217,19 @@ sub startup { } ); + $self->helper( + dbris => sub { + my ($self) = @_; + state $dbris = Travelynx::Helper::DBRIS->new( + log => $self->app->log, + cache => $self->app->cache_iris_rt, + root_url => $self->base_url_for('/')->to_abs, + user_agent => $self->ua, + version => $self->app->config->{version}, + ); + } + ); + $self->helper( hafas => sub { my ($self) = @_; @@ -452,6 +466,9 @@ sub startup { return Mojo::Promise->reject('You are already checked in'); } + if ( $opt{dbris} ) { + return $self->_checkin_dbris_p(%opt); + } if ( $opt{hafas} ) { return $self->_checkin_hafas_p(%opt); } @@ -530,6 +547,146 @@ sub startup { } ); + $self->helper( + '_checkin_dbris_p' => sub { + my ( $self, %opt ) = @_; + + my $station = $opt{station}; + my $train_id = $opt{train_id}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + my $hafas; + + my $promise = Mojo::Promise->new; + + $self->dbris->get_journey_p( + trip_id => $train_id, + with_polyline => 1 + )->then( + sub { + my ($journey) = @_; + my $found; + for my $stop ( $journey->route ) { + if ( $stop->eva eq $station ) { + $found = $stop; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts and $stop->sched_dep->epoch == $ts ) { + last; + } + } + } + if ( not $found ) { + $promise->reject( +"Did not find stop '$station' within journey '$train_id'" + ); + return; + } + for my $stop ( $journey->route ) { + $self->stations->add_or_update( + stop => $stop, + db => $db, + dbris => 'bahn.de', + ); + } + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $journey, + stop => $found, + data => { trip_id => $train_id }, + backend_id => $self->stations->get_backend_id( + dbris => 'bahn.de' + ), + ); + }; + if ($@) { + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $journey->polyline ) { + my @station_list; + my @coordinate_list; + for my $coord ( $journey->polyline ) { + if ( $coord->{stop} ) { + push( + @coordinate_list, + [ + $coord->{lon}, $coord->{lat}, + $coord->{stop}->eva + ] + ); + push( @station_list, $coord->{stop}->name ); + } + else { + push( @coordinate_list, + [ $coord->{lon}, $coord->{lat} ] ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $journey->train + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => ( $journey->route )[0]->eva, + to_eva => ( $journey->route )[-1]->eva, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + $self->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_departure => 1, + eva => $found->eva, + datetime => $found->sched_dep, + train_type => $journey->type, + train_no => $journey->number + ); + $self->add_stationinfo( $uid, 1, $train_id, + $found->eva ); + } + + $promise->resolve($journey); + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + + return $promise; + } + ); + $self->helper( '_checkin_hafas_p' => sub { my ( $self, %opt ) = @_; @@ -799,8 +956,8 @@ sub startup { return $promise->resolve( 0, 'race condition' ); } - if ( $user->{is_hafas} ) { - return $self->_checkout_hafas_p(%opt); + if ( $user->{is_dbris} or $user->{is_hafas} ) { + return $self->_checkout_journey_p(%opt); } my $now = DateTime->now( time_zone => 'Europe/Berlin' ); @@ -1049,7 +1206,7 @@ sub startup { ); $self->helper( - '_checkout_hafas_p' => sub { + '_checkout_journey_p' => sub { my ( $self, %opt ) = @_; my $station = $opt{station}; @@ -1840,6 +1997,7 @@ sub startup { cancellation => $latest_cancellation, backend_id => $latest->{backend_id}, backend_name => $latest->{backend_name}, + is_dbris => $latest->{is_dbris}, is_iris => $latest->{is_iris}, is_hafas => $latest->{is_hafas}, journey_id => $latest->{journey_id}, @@ -1901,8 +2059,10 @@ sub startup { ) ? \1 : \0, comment => $status->{comment}, backend => { - id => $status->{backend_id}, - type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS', + id => $status->{backend_id}, + type => $status->{ds_dbris} ? 'DBRIS' + : $status->{is_hafas} ? 'HAFAS' + : 'IRIS-TTS', name => $status->{backend_name}, }, fromStation => { diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 6ba80a3..f72a38c 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -2701,6 +2701,159 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;} } ); }, + + # v59 -> v60 + # Add bahn.de / DBRIS backend + sub { + my ($db) = @_; + $db->insert( + 'backends', + { + iris => 0, + hafas => 0, + efa => 0, + ris => 1, + name => 'bahn.de', + }, + ); + $db->query( + qq{ + update schema_version set version = 60; + } + ); + }, + + # v60 -> v61 + # Rename "ris" / "is_ris" to "dbris" / "is_dbris", as it is DB-specific + sub { + my ($db) = @_; + $db->query( + qq{ + drop view in_transit_str; + drop view journeys_str; + drop view users_with_backend; + drop view follows_in_transit; + alter table backends rename column ris to dbris; + create view in_transit_str as select + user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.name as backend_name, in_transit.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join backends as backend on in_transit.backend_id = backend.id + ; + create view journeys_str as select + journeys.id as journey_id, user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.name as backend_name, journeys.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, edited, route, messages, user_data, + dep_platform, arr_platform + from journeys + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and journeys.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and journeys.backend_id = arr_station.source + left join backends as backend on journeys.backend_id = backend.id + ; + create view users_with_backend as select + users.id as id, users.name as name, status, public_level, + email, password, registered_at, last_seen, + deletion_requested, deletion_notified, use_history, + accept_follows, notifications, profile, backend_id, iris, + hafas, efa, dbris, backend.name as backend_name + from users + left join backends as backend on users.backend_id = backend.id + ; + create view follows_in_transit as select + r1.subject_id as follower_id, user_id as followee_id, + users.name as followee_name, + train_type, train_line, train_no, train_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.name as backend_name, in_transit.backend_id as backend_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join relations as r1 on r1.predicate = 1 and r1.object_id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join backends as backend on in_transit.backend_id = backend.id + order by checkin_time desc + ; + update schema_version set version = 61; + } + ); + }, ); sub sync_stations { diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 5385e9b..55c2005 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -49,6 +49,111 @@ sub run { my $arr = $entry->{arr_eva}; my $train_id = $entry->{train_id}; + if ( $entry->{is_dbris} ) { + + eval { + + $self->app->dbris->get_journey_p( trip_id => $train_id )->then( + sub { + my ($journey) = @_; + + my $found_dep; + my $found_arr; + for my $stop ( $journey->route ) { + if ( $stop->eva == $dep ) { + $found_dep = $stop; + } + if ( $arr and $stop->eva == $arr ) { + $found_arr = $stop; + last; + } + } + if ( not $found_dep ) { + $self->app->log->debug( + "Did not find $dep within journey $train_id"); + return; + } + + if ( $found_dep->rt_dep ) { + $self->app->in_transit->update_departure_dbris( + uid => $uid, + journey => $journey, + stop => $found_dep, + dep_eva => $dep, + arr_eva => $arr, + train_id => $train_id, + ); + } + if ( $found_dep->sched_dep + and $found_dep->dep->epoch > $now->epoch ) + { + $self->app->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_departure => 1, + eva => $dep, + datetime => $found_dep->sched_dep, + train_type => $journey->type, + train_no => $journey->number, + ); + $self->app->add_stationinfo( $uid, 1, + $train_id, $found_dep->eva ); + } + + if ( $found_arr and $found_arr->rt_arr ) { + $self->app->in_transit->update_arrival_dbris( + uid => $uid, + journey => $journey, + stop => $found_arr, + dep_eva => $dep, + arr_eva => $arr + ); + if ( $found_arr->arr->epoch - $now->epoch < 600 ) { + $self->app->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_arrival => 1, + eva => $arr, + datetime => $found_arr->sched_dep, + train_type => $journey->type, + train_no => $journey->number, + ); + $self->app->add_stationinfo( $uid, 0, + $train_id, $found_dep->eva, + $found_arr->eva ); + } + } + } + )->catch( + sub { + my ($err) = @_; + $self->app->log->error( +"work($uid) @ DBRIS $entry->{backend_name}: journey: $err" + ); + } + )->wait; + + if ( $arr + and $entry->{real_arr_ts} + and $now->epoch - $entry->{real_arr_ts} > 600 ) + { + $self->app->checkout_p( + station => $arr, + force => 2, + dep_eva => $dep, + arr_eva => $arr, + uid => $uid + )->wait; + } + }; + if ($@) { + $errors += 1; + $self->app->log->error( + "work($uid) @ DBRIS $entry->{backend_name}: $@"); + } + next; + } + if ( $entry->{is_hafas} ) { eval { diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 9cd0edb..43b0683 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -1066,9 +1066,17 @@ sub backend_form { if ( $backend->{iris} ) { $type = 'IRIS-TTS'; $backend->{name} = 'IRIS'; - $backend->{longname} = 'Deutsche Bahn (IRIS-TTS)'; + $backend->{longname} = 'Deutsche Bahn: IRIS-TTS'; $backend->{homepage} = 'https://www.bahn.de'; } + elsif ( $backend->{dbris} ) { + $type = 'DBRIS'; + $backend->{longname} = 'Deutsche Bahn: bahn.de'; + $backend->{homepage} = 'https://www.bahn.de'; + + # not ready for production yet + $type = undef; + } elsif ( $backend->{hafas} ) { # These backends lack a journey endpoint or are no longer @@ -1135,14 +1143,11 @@ sub backend_form { $backend->{type} = $type; } - # These backends lack a journey endpoint and are useless for travelynx - @backends - = grep { $_->{name} ne 'Resrobot' and $_->{name} ne 'TPG' } @backends; - my $iris = shift @backends; - @backends - = sort { $a->{name} cmp $b->{name} } grep { $_->{type} } @backends; + @backends = map { $_->[1] } + sort { $a->[0] cmp $b->[0] } + map { [ lc( $_->{name} ), $_ ] } grep { $_->{type} } @backends; unshift( @backends, $iris ); diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 1ca9d4a..a6e56b9 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -42,6 +42,13 @@ sub get_connecting_trains_p { my $promise = Mojo::Promise->new; + if ( $user->{backend_dbris} ) { + + # We do get a little bit of via information, so this might work in some + # cases. But not reliably. Probably best to leave it out entirely then. + return $promise->reject; + } + if ( $opt{eva} ) { if ( $use_history & 0x01 ) { $eva = $opt{eva}; @@ -106,6 +113,8 @@ sub get_connecting_trains_p { my $iris_promise = Mojo::Promise->new; my %via_count = map { $_->{name} => 0 } @destinations; + my $backend + = $self->stations->get_backend( backend_id => $opt{backend_id} ); if ( $opt{backend_id} == 0 ) { $self->iris->get_departures_p( station => $eva, @@ -260,9 +269,11 @@ sub get_connecting_trains_p { } )->wait; } - else { - my $hafas_service - = $self->stations->get_hafas_name( backend_id => $opt{backend_id} ); + elsif ( $backend->{dbris} ) { + ...; + } + elsif ( $backend->{hafas} ) { + my $hafas_service = $backend->{name}; $self->hafas->get_departures_p( service => $hafas_service, eva => $eva, @@ -524,10 +535,19 @@ sub geolocation { return; } - my $hafas_service - = $self->stations->get_hafas_name( backend_id => $backend_id ); + my ( $dbris_service, $hafas_service ); + my $backend = $self->stations->get_backend( backend_id => $backend_id ); + if ( $backend->{dbris} ) { + $dbris_service = $backend->{name}; + } + elsif ( $backend->{hafas} ) { + $hafas_service = $backend->{name}; + } - if ($hafas_service) { + if ($dbris_service) { + ...; + } + elsif ($hafas_service) { $self->render_later; my $agent = $self->ua; @@ -588,6 +608,7 @@ sub geolocation { lon => $_->[0][3], lat => $_->[0][4], distance => $_->[1], + dbris => 0, hafas => 0, } } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon, @@ -656,6 +677,7 @@ sub travel_action { $promise->then( sub { return $self->checkin_p( + dbris => $params->{dbris}, hafas => $params->{hafas}, station => $params->{station}, train_id => $params->{train}, @@ -687,7 +709,10 @@ sub travel_action { my ( $still_checked_in, undef ) = @_; if ( my $destination = $params->{dest} ) { my $station_link = '/s/' . $destination; - if ( $status->{is_hafas} ) { + if ( $status->{is_dbris} ) { + $station_link .= '?dbris=' . $status->{backend_name}; + } + elsif ( $status->{is_hafas} ) { $station_link .= '?hafas=' . $status->{backend_name}; } $self->render( @@ -723,7 +748,10 @@ sub travel_action { sub { my ( $still_checked_in, $error ) = @_; my $station_link = '/s/' . $params->{station}; - if ( $status->{is_hafas} ) { + if ( $status->{is_dbris} ) { + $station_link .= '?dbris=' . $status->{backend_name}; + } + elsif ( $status->{is_hafas} ) { $station_link .= '?hafas=' . $status->{backend_name}; } @@ -774,7 +802,14 @@ sub travel_action { else { my $redir = '/'; if ( $status->{checked_in} or $status->{cancelled} ) { - if ( $status->{is_hafas} ) { + if ( $status->{is_dbris} ) { + $redir + = '/s/' + . $status->{dep_eva} + . '?dbris=' + . $status->{backend_name}; + } + elsif ( $status->{is_hafas} ) { $redir = '/s/' . $status->{dep_eva} @@ -796,6 +831,7 @@ sub travel_action { elsif ( $params->{action} eq 'cancelled_from' ) { $self->render_later; $self->checkin_p( + dbris => $params->{dbris}, hafas => $params->{hafas}, station => $params->{station}, train_id => $params->{train}, @@ -925,10 +961,19 @@ sub station { $timestamp = DateTime->now( time_zone => 'Europe/Berlin' ); } + my $dbris_service = $self->param('dbris') + // ( $user->{backend_dbris} ? $user->{backend_name} : undef ); my $hafas_service = $self->param('hafas') // ( $user->{backend_hafas} ? $user->{backend_name} : undef ); my $promise; - if ($hafas_service) { + if ($dbris_service) { + $promise = $self->dbris->get_departures_p( + station => $station, + timestamp => $timestamp, + lookbehind => 30, + ); + } + elsif ($hafas_service) { $promise = $self->hafas->get_departures_p( service => $hafas_service, eva => $station, @@ -954,7 +999,22 @@ sub station { my $now_within_range = abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0; - if ($hafas_service) { + if ($dbris_service) { + + @results = map { $_->[0] } + sort { $b->[1] <=> $a->[1] } + map { [ $_, $_->dep->epoch ] } $status->results; + + $status = { + station_eva => $station, + related_stations => [], + }; + + if ( $station =~ m{ [@] O = (? [^@]+ ) [@] }x ) { + $status->{station_name} = $+{name}; + } + } + elsif ($hafas_service) { @results = map { $_->[0] } sort { $b->[1] <=> $a->[1] } @@ -1039,6 +1099,7 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, hafas => $hafas_service, eva => $status->{station_eva}, datetime => $timestamp, @@ -1058,6 +1119,7 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, hafas => $hafas_service, eva => $status->{station_eva}, datetime => $timestamp, @@ -1076,6 +1138,7 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, hafas => $hafas_service, eva => $status->{station_eva}, datetime => $timestamp, @@ -1174,7 +1237,23 @@ sub redirect_to_station { my ($self) = @_; my $station = $self->param('station'); - $self->redirect_to("/s/${station}"); + if ( $self->param('backend_dbris') ) { + $self->render_later; + $self->dbris->get_station_id_p($station)->then( + sub { + my ($dbris_station) = @_; + $self->redirect_to( '/s/' . $dbris_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + $self->redirect_to('/'); + } + )->wait; + } + else { + $self->redirect_to("/s/${station}"); + } } sub cancelled { diff --git a/lib/Travelynx/Helper/DBRIS.pm b/lib/Travelynx/Helper/DBRIS.pm new file mode 100644 index 0000000..e647cc5 --- /dev/null +++ b/lib/Travelynx/Helper/DBRIS.pm @@ -0,0 +1,138 @@ +package Travelynx::Helper::DBRIS; + +# Copyright (C) 2025 Birte Kristina Friesel +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +use strict; +use warnings; +use 5.020; +use utf8; + +use DateTime; +use Encode qw(decode); +use JSON; +use Mojo::Promise; +use Mojo::UserAgent; +use Travel::Status::DE::DBRIS; + +sub new { + my ( $class, %opt ) = @_; + + my $version = $opt{version}; + + $opt{header} + = { 'User-Agent' => +"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx" + }; + + return bless( \%opt, $class ); +} + +sub get_station_id_p { + my ( $self, $station_name ) = @_; + my $promise = Mojo::Promise->new; + Travel::Status::DE::DBRIS->new_p( + locationSearch => $station_name, + cache => $self->{cache}, + lwp_options => { + timeout => 10, + agent => $self->{header}{'User-Agent'}, + }, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + )->then( + sub { + my ($dbris) = @_; + my $found; + for my $result ( $dbris->results ) { + if ( defined $result->eva ) { + $promise->resolve($result); + return; + } + } + $promise->reject("Unable to find station '$station_name'"); + return; + } + )->catch( + sub { + my ($err) = @_; + $promise->reject("'$err' while trying to look up '$station_name'"); + return; + } + )->wait; + return $promise; +} + +sub get_departures_p { + my ( $self, %opt ) = @_; + + my $agent = $self->{user_agent}; + + if ( $opt{station} =~ m{ [@] L = (? \d+ ) [@] }x ) { + $opt{station} = { + eva => $+{eva}, + id => $opt{station}, + }; + } + + my $when = ( + $opt{timestamp} + ? $opt{timestamp}->clone + : DateTime->now( time_zone => 'Europe/Berlin' ) + )->subtract( minutes => $opt{lookbehind} ); + return Travel::Status::DE::DBRIS->new_p( + station => $opt{station}, + datetime => $when, + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => $agent->request_timeout(10), + ); +} + +sub get_journey_p { + my ( $self, %opt ) = @_; + + my $promise = Mojo::Promise->new; + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + + my $agent = $self->{user_agent}; + if ( my $proxy = $self->{service_config}{dbris}{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + + Travel::Status::DE::DBRIS->new_p( + journey => $opt{trip_id}, + with_polyline => $opt{with_polyline}, + cache => $self->{realtime_cache}, + promise => 'Mojo::Promise', + user_agent => $agent->request_timeout(10), + )->then( + sub { + my ($dbris) = @_; + my $journey = $dbris->result; + + if ($journey) { + $self->{log}->debug("get_journey_p($opt{trip_id}): success"); + $promise->resolve($journey); + return; + } + $self->{log}->debug("get_journey_p($opt{trip_id}): no journey"); + $promise->reject('no journey'); + return; + } + )->catch( + sub { + my ($err) = @_; + $self->{log}->debug("get_journey_p($opt{trip_id}): error $err"); + $promise->reject($err); + return; + } + )->wait; + + return $promise; +} + +1; diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index 43ecb90..2b9832c 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -104,6 +104,8 @@ sub add { my $json = JSON->new; if ($train) { + + # IRIS $db->insert( 'in_transit', { @@ -134,7 +136,9 @@ sub add { } ); } - elsif ( $journey and $stop ) { + elsif ( $journey and $stop and $journey->can('id') ) { + + # HAFAS my @route; my $product = $journey->product_at( $stop->loc->eva ) // $journey->product; @@ -188,6 +192,56 @@ sub add { } ); } + elsif ( $journey and $stop ) { + + # DBRIS + my @route; + for my $j_stop ( $journey->route ) { + push( + @route, + [ + $j_stop->name, + $j_stop->eva, + { + sched_arr => _epoch( $j_stop->sched_arr ), + sched_dep => _epoch( $j_stop->sched_dep ), + rt_arr => _epoch( $j_stop->rt_arr ), + rt_dep => _epoch( $j_stop->rt_dep ), + arr_delay => $j_stop->arr_delay, + dep_delay => $j_stop->dep_delay, + load => undef, + lat => $j_stop->lat, + lon => $j_stop->lon, + } + ] + ); + } + $db->insert( + 'in_transit', + { + user_id => $uid, + cancelled => $stop->{dep_cancelled} + ? 1 + : 0, + checkin_station_id => $stop->eva, + checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ), + dep_platform => $stop->platform, + train_type => $journey->type, + train_no => $journey->number, + train_id => $data->{trip_id}, + sched_departure => $stop->sched_dep, + real_departure => $stop->rt_dep // $stop->sched_dep, + route => $json->encode( \@route ), + data => JSON->new->encode( + { + rt => $stop->{rt_dep} ? 1 : 0, + %{ $data // {} } + } + ), + backend_id => $backend_id, + } + ); + } else { die('neither train nor journey specified'); } @@ -731,6 +785,33 @@ sub update_departure_cancelled { return $rows; } +sub update_departure_dbris { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stop = $opt{stop}; + my $json = JSON->new; + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_departure => $stop->{rt_dep}, + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + sub update_departure_hafas { my ( $self, %opt ) = @_; my $uid = $opt{uid}; @@ -800,6 +881,55 @@ sub update_arrival { return $rows; } +sub update_arrival_dbris { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stop = $opt{stop}; + my $json = JSON->new; + + my @route; + for my $j_stop ( $journey->route ) { + push( + @route, + [ + $j_stop->name, + $j_stop->eva, + { + sched_arr => _epoch( $j_stop->sched_arr ), + sched_dep => _epoch( $j_stop->sched_dep ), + rt_arr => _epoch( $j_stop->rt_arr ), + rt_dep => _epoch( $j_stop->rt_dep ), + arr_delay => $j_stop->arr_delay, + dep_delay => $j_stop->dep_delay, + lat => $j_stop->lat, + lon => $j_stop->lon, + } + ] + ); + } + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_arrival => $stop->{rt_arr}, + route => $json->encode( [@route] ), + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + sub update_arrival_hafas { my ( $self, %opt ) = @_; my $uid = $opt{uid}; diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index 905c426..f5bc9f1 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -549,7 +549,7 @@ sub get { my @select = ( - qw(journey_id is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) + qw(journey_id is_dbris is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) ); my %where = ( user_id => $uid, @@ -607,6 +607,7 @@ sub get { my $ref = { id => $entry->{journey_id}, + is_dbris => $entry->{is_dbris}, is_iris => $entry->{is_iris}, is_hafas => $entry->{is_hafas}, backend_name => $entry->{backend_name}, @@ -870,8 +871,8 @@ sub get_latest_checkout_stations { my $res = $db->select( 'journeys_str', [ - 'arr_name', 'arr_eva', 'train_id', 'backend_id', - 'backend_name', 'is_hafas' + 'arr_name', 'arr_eva', 'train_id', 'backend_id', + 'backend_name', 'is_dbris', 'is_hafas' ], { user_id => $uid, @@ -895,6 +896,7 @@ sub get_latest_checkout_stations { { name => $row->{arr_name}, eva => $row->{arr_eva}, + dbris => $row->{is_dbris} ? $row->{backend_name} : 0, hafas => $row->{is_hafas} ? $row->{backend_name} : 0, backend_id => $row->{backend_id}, } @@ -1883,7 +1885,8 @@ sub get_connection_targets { ); my @destinations = $res->hashes->grep( sub { shift->{count} >= $min_count } ) - ->map( sub { shift->{dest} } )->each; + ->map( sub { shift->{dest} } ) + ->each; @destinations = $self->{stations}->get_by_evas( backend_id => $opt{backend_id}, evas => [@destinations] diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index 76fd452..3d6549f 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -25,11 +25,25 @@ sub get_backend_id { if ( $opt{hafas} and $self->{backend_id}{hafas}{ $opt{hafas} } ) { return $self->{backend_id}{hafas}{ $opt{hafas} }; } + if ( $opt{dbris} and $self->{backend_id}{dbris}{ $opt{dbris} } ) { + return $self->{backend_id}{dbris}{ $opt{dbris} }; + } my $db = $opt{db} // $self->{pg}->db; my $backend_id = 0; - if ( $opt{hafas} ) { + if ( $opt{dbris} ) { + $backend_id = $db->select( + 'backends', + ['id'], + { + dbris => 1, + name => $opt{dbris} + } + )->hash->{id}; + $self->{backend_id}{dbris}{ $opt{dbris} } = $backend_id; + } + elsif ( $opt{hafas} ) { $backend_id = $db->select( 'backends', ['id'], @@ -44,31 +58,25 @@ sub get_backend_id { return $backend_id; } -sub get_hafas_name { +sub get_backend { my ( $self, %opt ) = @_; - if ( exists $self->{hafas_name}{ $opt{backend_id} } ) { - return $self->{hafas_name}{ $opt{backend_id} }; + if ( $self->{backend_cache}{ $opt{backend_id} } ) { + return $self->{backend_cache}{ $opt{backend_id} }; } - my $db = $opt{db} // $self->{pg}->db; - my $hafas_name; + my $db = $opt{db} // $self->{pg}->db; my $ret = $db->select( 'backends', - ['name'], + '*', { - hafas => 1, - id => $opt{backend_id}, + id => $opt{backend_id}, } )->hash; - if ($ret) { - $hafas_name = $ret->{name}; - } - - $self->{hafas_name}{ $opt{backend_id} } = $hafas_name; + $self->{backend_cache}{ $opt{backend_id} } = $ret; - return $hafas_name; + return $ret; } sub get_backends { @@ -76,7 +84,8 @@ sub get_backends { $opt{db} //= $self->{pg}->db; - my $res = $opt{db}->select( 'backends', [ 'id', 'name', 'iris', 'hafas' ] ); + my $res = $opt{db} + ->select( 'backends', [ 'id', 'name', 'iris', 'hafas', 'dbris' ] ); my @ret; while ( my $row = $res->hash ) { @@ -86,6 +95,7 @@ sub get_backends { id => $row->{id}, name => $row->{name}, iris => $row->{iris}, + dbris => $row->{dbris}, hafas => $row->{hafas}, } ); @@ -97,11 +107,49 @@ sub get_backends { sub add_or_update { my ( $self, %opt ) = @_; my $stop = $opt{stop}; - my $loc = $stop->loc; $opt{db} //= $self->{pg}->db; $opt{backend_id} //= $self->get_backend_id(%opt); + if ( $opt{dbris} ) { + if ( + my $s = $self->get_by_eva( + $stop->eva, + db => $opt{db}, + backend_id => $opt{backend_id} + ) + ) + { + $opt{db}->update( + 'stations', + { + name => $stop->name, + lat => $stop->lat, + lon => $stop->lon, + archived => 0 + }, + { + eva => $stop->eva, + source => $opt{backend_id} + } + ); + return; + } + $opt{db}->insert( + 'stations', + { + eva => $stop->eva, + name => $stop->name, + lat => $stop->lat, + lon => $stop->lon, + source => $opt{backend_id}, + archived => 0 + } + ); + return; + } + + my $loc = $stop->loc; if ( my $s = $self->get_by_eva( $loc->eva, diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 7d3777b..e3d6f7a 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -209,7 +209,11 @@ sub set_backend { my ( $self, %opt ) = @_; $opt{db} //= $self->{pg}->db; - $opt{db}->update('users', {backend_id => $opt{backend_id}}, {id => $opt{uid}}); + $opt{db}->update( + 'users', + { backend_id => $opt{backend_id} }, + { id => $opt{uid} } + ); } sub set_privacy { @@ -414,7 +418,7 @@ sub get { . 'extract(epoch from registered_at) as registered_at_ts, ' . 'extract(epoch from last_seen) as last_seen_ts, ' . 'extract(epoch from deletion_requested) as deletion_requested_ts, ' - . 'backend_id, backend_name, hafas', + . 'backend_id, backend_name, hafas, dbris', { id => $uid } )->hash; if ($user) { @@ -453,6 +457,7 @@ sub get { : undef, backend_id => $user->{backend_id}, backend_name => $user->{backend_name}, + backend_dbris => $user->{dbris}, backend_hafas => $user->{hafas}, }; } diff --git a/public/static/js/travelynx-actions.js b/public/static/js/travelynx-actions.js index d4ddf45..0d2de5a 100644 --- a/public/static/js/travelynx-actions.js +++ b/public/static/js/travelynx-actions.js @@ -194,6 +194,7 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'checkin', + dbris: link.data('dbris'), hafas: link.data('hafas'), station: link.data('station'), train: link.data('train'), @@ -206,6 +207,7 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'checkout', + dbris: link.data('dbris'), hafas: link.data('hafas'), station: link.data('station'), force: link.data('force'), @@ -237,6 +239,7 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'cancelled_from', + dbris: link.data('dbris'), hafas: link.data('hafas'), station: link.data('station'), ts: link.data('ts'), @@ -248,6 +251,7 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'cancelled_to', + dbris: link.data('dbris'), hafas: link.data('hafas'), station: link.data('station'), force: true, diff --git a/templates/_departures_dbris.html.ep b/templates/_departures_dbris.html.ep new file mode 100644 index 0000000..14426e8 --- /dev/null +++ b/templates/_departures_dbris.html.ep @@ -0,0 +1,54 @@ + diff --git a/templates/departures.html.ep b/templates/departures.html.ep index a86a7b5..6e2a98a 100644 --- a/templates/departures.html.ep +++ b/templates/departures.html.ep @@ -9,7 +9,10 @@
% my $self_link = url_for('sstation', station => $station // param('station')); - % if (param('hafas')) { + % if (param('dbris')) { + <%= param('dbris') %> + % } + % elsif (param('hafas')) { <%= param('hafas') %> % } % else { @@ -93,8 +96,8 @@
- % if ($hafas) { - früher + % if ($dbris or $hafas) { + früher % }
@@ -103,8 +106,8 @@ % }
- % if ($hafas) { - später + % if ($dbris or $hafas) { + später % }
@@ -139,7 +142,10 @@ % }

% if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) { - % if ($hafas) { + % if ($dbris) { + %= include '_departures_dbris', results => $results, dbris => $dbris; + % } + % elsif ($hafas) { %= include '_departures_hafas', results => $results, hafas => $hafas; % } % else { diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep index 8b6eb3f..856fdef 100644 --- a/templates/landingpage.html.ep +++ b/templates/landingpage.html.ep @@ -60,6 +60,7 @@ + %= hidden_field backend_dbris => $user->{backend_dbris}
%= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef -- cgit v1.2.3 From 4435e0942d9385572eedb7b2fe11787cd8168e20 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Sun, 23 Mar 2025 18:20:36 +0100 Subject: sync_backends: ris column has been renamed to dbris. oops. --- lib/Travelynx/Command/database.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/Travelynx/Command/database.pm') diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index f72a38c..d0bc163 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -3062,7 +3062,7 @@ sub sync_backends { iris => 0, hafas => 1, efa => 0, - ris => 0, + dbris => 0, name => $service->{shortname}, }, { on_conflict => undef } -- cgit v1.2.3 From ccdfc0206f849ccaeee8f5a5093c51d1274d2652 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Mon, 9 Jun 2025 13:13:56 +0200 Subject: Add (possibly still somewhat experimental) MOTIS support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit c7c8b2ec5d8254eefb548bfe7763a7d8c9558be4 Author: Birte Kristina Friesel Date: Mon Jun 9 13:08:57 2025 +0200 fix another merge issue commit d2ae55c901ab59284263ad3070ba425e03cee833 Author: Birte Kristina Friesel Date: Mon Jun 9 13:08:39 2025 +0200 Stations: get_by_external_id is a slow function commit 725174413300e71c350d2f1dcfbeacd751def977 Author: Birte Kristina Friesel Date: Mon Jun 9 13:05:48 2025 +0200 ... I accidentally commited a merge conflict commit c695494dbd6aaf252199da42ad763bdffa1d64b9 Merge: e5da62b 3322ca2 Author: Birte Kristina Friesel Date: Mon Jun 9 12:46:08 2025 +0200 Merge branch 'main' into motis commit e5da62bcfc7953d5109ba53ae1fcc34f509f251b Author: Birte Kristina Friesel Date: Wed Apr 30 18:15:39 2025 +0200 cpanfile: add Travel::Status::MOTIS dependency commit 180723a9e0e2f0aede0bc6352d5eee601183ccef Merge: 479373b c90ae4c Author: Birte Kristina Friesel Date: Wed Apr 30 18:13:45 2025 +0200 Merge branch 'main' into motis commit 479373b14eaadbc022199df246c9fb523a87188c Author: Birte Kristina Friesel Date: Wed Apr 30 18:06:41 2025 +0200 database: remove duplicate users_with_backend migration commit 94c8b5a7d1e2cb7f73b0eca7e33d916775504cd4 Author: Birte Kristina Friesel Date: Wed Apr 30 18:06:04 2025 +0200 Do not store train colours in database. They're only supported by MOTIS. commit d58f23c3c7b06cc0243c1945dacd8673d2d2e428 Author: networkException Date: Fri Apr 18 11:47:02 2025 +0200 Initial MOTIS backend support This patch adds support for checkins using MOTIS backends using the Travel::Status::MOTIS module. With this travelynx supports the two services currently exposed by the module, RNV for local transit in Mannheim, Germany and surrounding cities and transitous for worldwide crowdsourced tranit feeds. This implementation supports realtime predictions, cancellations and polylines as well as custom route colors if available. As MOTIS doesn't expose names of indivial trips currently, displaying transports is mostly limited to route names. MOTIS uses strings for stop ids, based on the used GTFS source feeds. As travelynx's data model currently assumes interger station ids, this patch adds a mapping table to the database. This patch assumes support for MOTIS in db-fakedisplay. Note that while träwelling has migrated to tranitous fully sync remains unsupported for now. See https://github.com/Traewelling/traewelling/issues/3345 --- cpanfile | 1 + lib/Travelynx.pm | 170 ++++++++++++++++++++++++++- lib/Travelynx/Command/database.pm | 214 +++++++++++++++++++++++++++++++++- lib/Travelynx/Command/work.pm | 95 +++++++++++++++ lib/Travelynx/Controller/Account.pm | 47 ++++++++ lib/Travelynx/Controller/Api.pm | 2 + lib/Travelynx/Controller/Traveling.pm | 136 ++++++++++++++++++++- lib/Travelynx/Helper/MOTIS.pm | 158 +++++++++++++++++++++++++ lib/Travelynx/Model/InTransit.pm | 131 ++++++++++++++++++++- lib/Travelynx/Model/Journeys.pm | 28 +++-- lib/Travelynx/Model/Stations.pm | 96 ++++++++++++++- lib/Travelynx/Model/Users.pm | 9 +- public/static/js/geolocation.js | 22 +++- public/static/js/travelynx-actions.js | 24 ++-- sass/src/common/local.scss | 18 +-- templates/_backend_line.html.ep | 2 +- templates/_departures_motis.html.ep | 54 +++++++++ templates/_format_train.html.ep | 4 +- templates/_history_trains.html.ep | 5 +- templates/changelog.html.ep | 14 +++ templates/departures.html.ep | 18 ++- templates/landingpage.html.ep | 2 +- 22 files changed, 1191 insertions(+), 59 deletions(-) create mode 100644 lib/Travelynx/Helper/MOTIS.pm create mode 100644 templates/_departures_motis.html.ep (limited to 'lib/Travelynx/Command/database.pm') diff --git a/cpanfile b/cpanfile index 85ef852..758acbf 100644 --- a/cpanfile +++ b/cpanfile @@ -17,6 +17,7 @@ requires 'Mojolicious::Plugin::OAuth2'; requires 'Mojo::Pg'; requires 'Text::CSV'; requires 'Text::Markdown'; +requires 'Travel::Status::MOTIS', '>= 0.01'; requires 'Travel::Status::DE::DBRIS', '>= 0.10'; requires 'Travel::Status::DE::HAFAS', '>= 6.20'; requires 'Travel::Status::DE::IRIS'; diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index af46e5a..0429d5e 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -1,6 +1,7 @@ package Travelynx; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -24,6 +25,7 @@ use Travelynx::Helper::DBDB; use Travelynx::Helper::DBRIS; use Travelynx::Helper::HAFAS; use Travelynx::Helper::IRIS; +use Travelynx::Helper::MOTIS; use Travelynx::Helper::Sendmail; use Travelynx::Helper::Traewelling; use Travelynx::Model::InTransit; @@ -259,6 +261,18 @@ sub startup { } ); + $self->helper( + motis => sub { + my ($self) = @_; + state $motis = Travelynx::Helper::MOTIS->new( + log => $self->app->log, + cache => $self->app->cache_iris_rt, + user_agent => $self->ua, + version => $self->app->config->{version}, + ); + } + ); + $self->helper( traewelling => sub { my ($self) = @_; @@ -475,6 +489,9 @@ sub startup { return Mojo::Promise->reject('You are already checked in'); } + if ( $opt{motis} ) { + return $self->_checkin_motis_p(%opt); + } if ( $opt{dbris} ) { return $self->_checkin_dbris_p(%opt); } @@ -556,6 +573,147 @@ sub startup { } ); + $self->helper( + '_checkin_motis_p' => sub { + my ( $self, %opt ) = @_; + + my $station = $opt{station}; + my $train_id = $opt{train_id}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + my $hafas; + + my $promise = Mojo::Promise->new; + + $self->motis->get_trip_p( + service => $opt{motis}, + trip_id => $train_id, + )->then( + sub { + my ($trip) = @_; + my $found_stopover; + + for my $stopover ( $trip->stopovers ) { + if ( $stopover->stop->id eq $station ) { + $found_stopover = $stopover; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts and $stopover->scheduled_departure->epoch == $ts ) { + last; + } + } + } + + if ( not $found_stopover ) { + $promise->reject("Did not find stopover at '$station' within trip '$train_id'"); + return; + } + + for my $stopover ( $trip->stopovers ) { + $self->stations->add_or_update( + stop => $stopover->stop, + db => $db, + motis => $opt{motis}, + ); + } + + $self->stations->add_or_update( + stop => $found_stopover->stop, + db => $db, + motis => $opt{motis}, + ); + + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $trip, + stopover => $found_stopover, + data => { trip_id => $train_id }, + backend_id => $self->stations->get_backend_id( + motis => $opt{motis} + ), + ); + }; + + if ($@) { + $self->app->log->error("Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $trip->polyline ) { + my @station_list; + my @coordinate_list; + for my $coordinate ( $trip->polyline ) { + if ( $coordinate->{stop} ) { + if ( not defined $coordinate->{stop}->{eva} ) { + die() + } + + push( + @coordinate_list, + [ + $coordinate->{lon}, $coordinate->{lat}, + $coordinate->{stop}->{eva} + ] + ); + + push( @station_list, $coordinate->{stop}->name ); + } + else { + push( @coordinate_list, [ $coordinate->{lon}, $coordinate->{lat} ] ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $trip->route_name + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => ( $trip->stopovers )[0]->stop->{eva}, + to_eva => ( $trip->stopovers )[-1]->stop->{eva}, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + } + + $promise->resolve($trip); + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + + return $promise; + } + ); + $self->helper( '_checkin_dbris_p' => sub { my ( $self, %opt ) = @_; @@ -966,7 +1124,7 @@ sub startup { return $promise->resolve( 0, 'race condition' ); } - if ( $user->{is_dbris} or $user->{is_hafas} ) { + if ( $user->{is_dbris} or $user->{is_hafas} or $user->{is_motis} ) { return $self->_checkout_journey_p(%opt); } @@ -2052,6 +2210,7 @@ sub startup { is_dbris => $latest->{is_dbris}, is_iris => $latest->{is_iris}, is_hafas => $latest->{is_hafas}, + is_motis => $latest->{is_motis}, journey_id => $latest->{journey_id}, timestamp => $action_time, timestamp_delta => $now->epoch - $action_time->epoch, @@ -2063,6 +2222,7 @@ sub startup { real_departure => epoch_to_dt( $latest->{real_dep_ts} ), dep_ds100 => $latest->{dep_ds100}, dep_eva => $latest->{dep_eva}, + dep_external_id => $latest->{dep_external_id}, dep_name => $latest->{dep_name}, dep_lat => $latest->{dep_lat}, dep_lon => $latest->{dep_lon}, @@ -2071,6 +2231,7 @@ sub startup { real_arrival => epoch_to_dt( $latest->{real_arr_ts} ), arr_ds100 => $latest->{arr_ds100}, arr_eva => $latest->{arr_eva}, + arr_external_id => $latest->{arr_external_id}, arr_name => $latest->{arr_name}, arr_lat => $latest->{arr_lat}, arr_lon => $latest->{arr_lon}, @@ -2106,7 +2267,7 @@ sub startup { my $ret = { deprecated => \0, checkedIn => ( - $status->{checked_in} + $status->{checked_in} or $status->{cancelled} ) ? \1 : \0, comment => $status->{comment}, @@ -2114,6 +2275,7 @@ sub startup { id => $status->{backend_id}, type => $status->{is_dbris} ? 'DBRIS' : $status->{is_hafas} ? 'HAFAS' + : $status->{is_motis} ? 'MOTIS' : 'IRIS-TTS', name => $status->{backend_name}, }, @@ -2538,8 +2700,8 @@ sub startup { color => '#673ab7', opacity => @polylines ? $with_polyline - ? 0.4 - : 0.6 + ? 0.4 + : 0.6 : 0.8, }, { diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index d0bc163..0e87b2a 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1,6 +1,7 @@ package Travelynx::Command::database; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Command'; @@ -11,6 +12,7 @@ use List::Util qw(); use JSON; use Travel::Status::DE::HAFAS; use Travel::Status::DE::IRIS::Stations; +use Travel::Status::MOTIS; has description => 'Initialize or upgrade database layout'; @@ -2854,6 +2856,173 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;} } ); }, + + # v61 -> v62 + # Add MOTIS backend type, add RNV and transitous MOTIS backends + sub { + my ($db) = @_; + $db->query( + qq{ + alter table backends add column motis bool default false; + alter table schema_version add column motis varchar(12); + + create table stations_external_ids ( + eva serial not null primary key, + backend_id smallint not null, + external_id text not null, + + unique (backend_id, external_id), + foreign key (eva, backend_id) references stations (eva, source) + ); + + create view stations_with_external_ids as select + stations.*, stations_external_ids.external_id + from stations + left join stations_external_ids on + stations.eva = stations_external_ids.eva and + stations.source = stations_external_ids.backend_id + ; + + drop view in_transit_str; + drop view journeys_str; + drop view users_with_backend; + drop view follows_in_transit; + + create view in_transit_str as select + user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.motis as is_motis, + backend.name as backend_name, in_transit.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + dep_station_external_id.external_id as dep_external_id, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + arr_station_external_id.external_id as arr_external_id, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join stations_external_ids as dep_station_external_id on checkin_station_id = dep_station_external_id.eva and in_transit.backend_id = dep_station_external_id.backend_id + left join stations_external_ids as arr_station_external_id on checkout_station_id = arr_station_external_id.eva and in_transit.backend_id = arr_station_external_id.backend_id + left join backends as backend on in_transit.backend_id = backend.id + ; + create view journeys_str as select + journeys.id as journey_id, user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.motis as is_motis, + backend.name as backend_name, journeys.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + dep_station_external_id.external_id as dep_external_id, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + arr_station_external_id.external_id as arr_external_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, edited, route, messages, user_data, + dep_platform, arr_platform + from journeys + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and journeys.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and journeys.backend_id = arr_station.source + left join stations_external_ids as dep_station_external_id on checkin_station_id = dep_station_external_id.eva and journeys.backend_id = dep_station_external_id.backend_id + left join stations_external_ids as arr_station_external_id on checkout_station_id = arr_station_external_id.eva and journeys.backend_id = arr_station_external_id.backend_id + left join backends as backend on journeys.backend_id = backend.id + ; + create view users_with_backend as select + users.id as id, users.name as name, status, public_level, + email, password, registered_at, last_seen, + deletion_requested, deletion_notified, use_history, + accept_follows, notifications, profile, backend_id, iris, + hafas, efa, dbris, motis, backend.name as backend_name + from users + left join backends as backend on users.backend_id = backend.id + ; + create view follows_in_transit as select + r1.subject_id as follower_id, user_id as followee_id, + users.name as followee_name, + train_type, train_line, train_no, train_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.motis as is_motis, + backend.name as backend_name, in_transit.backend_id as backend_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join relations as r1 on r1.predicate = 1 and r1.object_id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join backends as backend on in_transit.backend_id = backend.id + order by checkin_time desc + ; + } + ); + $db->query( + qq{ + update schema_version set version = 62; + } + ); + }, ); sub sync_stations { @@ -3044,7 +3213,7 @@ sub sync_stations { } } -sub sync_backends { +sub sync_backends_hafas { my ($db) = @_; for my $service ( Travel::Status::DE::HAFAS::get_services() ) { my $present = $db->select( @@ -3074,6 +3243,36 @@ sub sync_backends { { hafas => $Travel::Status::DE::HAFAS::VERSION } ); } +sub sync_backends_motis { + my ($db) = @_; + for my $service ( Travel::Status::MOTIS::get_services() ) { + my $present = $db->select( + 'backends', + 'count(*) as count', + { + motis => 1, + name => $service->{shortname} + } + )->hash->{count}; + if ( not $present ) { + $db->insert( + 'backends', + { + iris => 0, + hafas => 0, + efa => 0, + dbris => 0, + motis => 1, + name => $service->{shortname}, + }, + { on_conflict => undef } + ); + } + } + + $db->update( 'schema_version', { motis => $Travel::Status::MOTIS::VERSION } ); +} + sub setup_db { my ($db) = @_; my $tx = $db->begin; @@ -3169,7 +3368,18 @@ sub migrate_db { else { say "Synchronizing with Travel::Status::DE::HAFAS $Travel::Status::DE::HAFAS::VERSION"; - sync_backends($db); + sync_backends_hafas($db); + } + + my $motis_version = get_schema_version( $db, 'motis' ) // '0'; + say "Found backend table for Motis v${motis_version}"; + if ( $motis_version eq $Travel::Status::MOTIS::VERSION ) { + say 'Backend table is up-to-date'; + } + else { + say +"Synchronizing with Travel::Status::MOTIS $Travel::Status::MOTIS::VERSION"; + sync_backends_motis($db); } $db->update( 'schema_version', diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 4876e98..4ff5c9e 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -1,6 +1,7 @@ package Travelynx::Command::work; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Command'; @@ -184,6 +185,100 @@ sub run { next; } + if ( $entry->{is_motis} ) { + + eval { + $self->app->motis->trip_id( + service => $entry->{backend_name}, + trip_id => $train_id, + )->then( + sub { + my ($journey) = @_; + + for my $stopover ( $journey->stopovers ) { + if ( not defined $stopover->stop->{eva} ) { + my $stop = $self->app->stations->get_by_external_id( + external_id => $stopover->stop->id, + motis => $entry->{backend_name}, + ); + + $stopover->stop->{eva} = $stop->{eva}; + } + } + + my $found_departure; + my $found_arrival; + for my $stopover ( $journey->stopovers ) { + if ( $stopover->stop->{eva} == $dep ) { + $found_departure = $stopover; + } + + if ( $arr and $stopover->stop->{eva} == $arr ) { + $found_arrival = $stopover; + last; + } + } + + if ( not $found_departure ) { + $self->app->log->debug("Did not find $dep within trip $train_id"); + return; + } + + if ( $found_departure->realtime_departure ) { + $self->app->in_transit->update_departure_motis( + uid => $uid, + journey => $journey, + stopover => $found_departure, + dep_eva => $dep, + arr_eva => $arr, + train_id => $train_id, + ); + } + + if ( $found_arrival and $found_arrival->realtime_arrival ) { + $self->app->in_transit->update_arrival_motis( + uid => $uid, + journey => $journey, + train_id => $train_id, + stopover => $found_arrival, + dep_eva => $dep, + arr_eva => $arr + ); + } + } + )->catch( + sub { + my ($err) = @_; + $self->app->log->error( +"work($uid) @ MOTIS $entry->{backend_name}: journey: $err" + ); + if ( $err =~ m{HTTP 429} ) { + $dbris_rate_limited = 1; + } + } + )->wait; + + if ( $arr + and $entry->{real_arr_ts} + and $now->epoch - $entry->{real_arr_ts} > 600 ) + { + $self->app->checkout_p( + station => $arr, + force => 2, + dep_eva => $dep, + arr_eva => $arr, + uid => $uid + )->wait; + } + }; + if ($@) { + $errors += 1; + $self->app->log->error( + "work($uid) @ MOTIS $entry->{backend_name}: $@"); + } + next; + } + if ( $entry->{is_hafas} ) { eval { diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index e36dcc3..4c69f91 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -1,6 +1,7 @@ package Travelynx::Controller::Account; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; @@ -1137,6 +1138,52 @@ sub backend_form { $type = undef; } } + elsif ( $backend->{motis} ) { + my $s = $self->motis->get_service( $backend->{name} ); + + $type = 'MOTIS'; + $backend->{longname} = $s->{name}; + $backend->{homepage} = $s->{homepage}; + $backend->{regions} = [ map { $place_map{$_} // $_ } + @{ $s->{coverage}{regions} // [] } ]; + $backend->{has_area} = $s->{coverage}{area} ? 1 : 0; + + if ( $backend->{name} eq 'transitous' ) { + $backend->{regions} = [ 'Weltweit' ]; + } + if ( $backend->{name} eq 'RNV' ) { + $backend->{homepage} = 'https://rnv-online.de/'; + } + + if ( + $s->{coverage}{area} + and $s->{coverage}{area}{type} eq 'Polygon' + and $self->lonlat_in_polygon( + $s->{coverage}{area}{coordinates}, + [ $user_lon, $user_lat ] + ) + ) + { + push( @suggested_backends, $backend ); + } + elsif ( $s->{coverage}{area} + and $s->{coverage}{area}{type} eq 'MultiPolygon' ) + { + for my $s_poly ( + @{ $s->{coverage}{area}{coordinates} // [] } ) + { + if ( + $self->lonlat_in_polygon( + $s_poly, [ $user_lon, $user_lat ] + ) + ) + { + push( @suggested_backends, $backend ); + last; + } + } + } + } $backend->{type} = $type; } diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index 9fe72b2..572d3fa 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -189,6 +189,7 @@ sub travel_v1 { my $train_id; my $dbris = sanitize( undef, $payload->{dbris} ); my $hafas = sanitize( undef, $payload->{hafas} ); + my $motis = sanitize( undef, $payload->{motis} ); if ( not $hafas and exists $payload->{train}{journeyID} ) { $dbris //= 'bahn.de'; @@ -298,6 +299,7 @@ sub travel_v1 { uid => $uid, hafas => $hafas, dbris => $dbris, + motis => $motis, ); } )->then( diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 0d89fb9..aa7ee9b 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -1,6 +1,7 @@ package Travelynx::Controller::Traveling; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; @@ -48,6 +49,11 @@ sub get_connecting_trains_p { # cases. But not reliably. Probably best to leave it out entirely then. return $promise->reject; } + if ( $user->{backend_motis} ) { + + # FIXME: The following code can't handle external_ids currently + return $promise->reject; + } if ( $opt{eva} ) { if ( $use_history & 0x01 ) { @@ -408,8 +414,8 @@ sub homepage { } } else { - @recent_targets = uniq_by { $_->{eva} } - $self->journeys->get_latest_checkout_stations( uid => $uid ); + @recent_targets = uniq_by { $_->{external_id_or_eva} } + $self->journeys->get_latest_checkout_stations( uid => $uid ); } $self->render( 'landingpage', @@ -550,7 +556,7 @@ sub geolocation { return; } - my ( $dbris_service, $hafas_service ); + my ( $dbris_service, $hafas_service, $motis_service ); my $backend = $self->stations->get_backend( backend_id => $backend_id ); if ( $backend->{dbris} ) { $dbris_service = $backend->{name}; @@ -558,6 +564,9 @@ sub geolocation { elsif ( $backend->{hafas} ) { $hafas_service = $backend->{name}; } + elsif ( $backend->{motis} ) { + $motis_service = $backend->{name}; + } if ($dbris_service) { $self->render_later; @@ -654,6 +663,54 @@ sub geolocation { return; } + elsif ($motis_service) { + $self->render_later; + + Travel::Status::MOTIS->new_p( + promise => 'Mojo::Promise', + user_agent => $self->ua, + + service => $motis_service, + stops_by_coordinate => { + lat => $lat, + lon => $lon + } + )->then( + sub { + my ($motis) = @_; + my @motis = map { + { + id => $_->id, + name => $_->name, + distance => 0, + motis => $motis_service, + } + } $motis->results; + + if ( @motis > 10 ) { + @motis = @motis[ 0 .. 9 ]; + } + + $self->render( + json => { + candidates => [@motis], + } + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + json => { + candidates => [], + warning => $err, + } + ); + } + )->wait; + + return; + } my @iris = map { { @@ -734,6 +791,7 @@ sub travel_action { return $self->checkin_p( dbris => $params->{dbris}, hafas => $params->{hafas}, + motis => $params->{motis}, station => $params->{station}, train_id => $params->{train}, train_suffix => $params->{suffix}, @@ -872,6 +930,13 @@ sub travel_action { . '?hafas=' . $status->{backend_name}; } + elsif ( $status->{is_motis} ) { + $redir + = '/s/' + . $status->{dep_external_id} + . '?motis=' + . $status->{backend_name}; + } else { $redir = '/s/' . $status->{dep_ds100}; } @@ -889,6 +954,7 @@ sub travel_action { $self->checkin_p( dbris => $params->{dbris}, hafas => $params->{hafas}, + motis => $params->{motis}, station => $params->{station}, train_id => $params->{train}, ts => $params->{ts}, @@ -1021,6 +1087,8 @@ sub station { // ( $user->{backend_dbris} ? $user->{backend_name} : undef ); my $hafas_service = $self->param('hafas') // ( $user->{backend_hafas} ? $user->{backend_name} : undef ); + my $motis_service = $self->param('motis') + // ( $user->{backend_motis} ? $user->{backend_name} : undef ); my $promise; if ($dbris_service) { if ( $station !~ m{ [@] L = \d+ }x ) { @@ -1053,6 +1121,35 @@ sub station { lookahead => 30, ); } + elsif ($motis_service) { + if ( $station !~ m/.*_.*/ ) { + $self->render_later; + $self->motis->get_station_by_query_p( + service => $motis_service, + query => $station, + )->then( + sub { + my ($motis_station) = @_; + $self->redirect_to( '/s/' . $motis_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + say "$err"; + + $self->redirect_to('/'); + } + )->wait; + return; + } + $promise = $self->motis->get_departures_p( + service => $motis_service, + station_id => $station, + timestamp => $timestamp, + lookbehind => 30, + lookahead => 30, + ) + } else { $promise = $self->iris->get_departures_p( station => $station, @@ -1106,6 +1203,17 @@ sub station { related_stations => [], }; } + elsif ($motis_service) { + @results = map { $_->[0] } + sort { $b->[1] <=> $a->[1] } + map { [ $_, $_->stopover->departure->epoch ] } $status->results; + + $status = { + station_eva => $station, + station_name => $status->{results}->[0]->stopover->stop->name, + related_stations => [], + }; + } else { # You can't check into a train which terminates here @@ -1172,6 +1280,7 @@ sub station { user => $user, dbris => $dbris_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1192,6 +1301,7 @@ sub station { user => $user, dbris => $dbris_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1211,6 +1321,7 @@ sub station { user => $user, dbris => $dbris_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1322,6 +1433,23 @@ sub redirect_to_station { } )->wait; } + elsif ( $self->param('backend_motis') ) { + $self->render_later; + $self->motis->get_station_by_query( + service => $self->param('backend_motis'), + query => $station, + )->then( + sub { + my ($motis_station) = @_; + $self->redirect_to( '/s/' . $motis_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + $self->redirect_to('/'); + } + )->wait; + } else { $self->redirect_to("/s/${station}"); } @@ -1892,7 +2020,7 @@ sub journey_details { $delay = sprintf( 'mit %+d ', ( - $journey->{rt_arrival}->epoch + $journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch ) / 60 ); diff --git a/lib/Travelynx/Helper/MOTIS.pm b/lib/Travelynx/Helper/MOTIS.pm new file mode 100644 index 0000000..ee2b10b --- /dev/null +++ b/lib/Travelynx/Helper/MOTIS.pm @@ -0,0 +1,158 @@ +package Travelynx::Helper::MOTIS; + +# Copyright (C) 2025 networkException +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +use strict; +use warnings; +use 5.020; +use utf8; + +use DateTime; +use Encode qw(decode); +use JSON; +use Mojo::Promise; +use Mojo::UserAgent; + +use Travel::Status::MOTIS; + +sub _epoch { + my ($dt) = @_; + + return $dt ? $dt->epoch : 0; +} + +sub new { + my ( $class, %opt ) = @_; + + my $version = $opt{version}; + + $opt{header} + = { 'User-Agent' => +"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx" + }; + + return bless( \%opt, $class ); +} + +sub get_service { + my ( $self, $service ) = @_; + + return Travel::Status::MOTIS::get_service($service); +} + +sub get_station_by_query_p { + my ( $self, %opt ) = @_; + + $opt{service} //= 'transitous'; + + my $promise = Mojo::Promise->new; + + Travel::Status::MOTIS->new_p( + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + lwp_options => { + timeout => 10, + agent => $self->{header}{'User-Agent'}, + }, + + service => $opt{service}, + stops_by_query => $opt{query}, + )->then( + sub { + my ($motis) = @_; + my $found; + + for my $result ( $motis->results ) { + if ( defined $result->id ) { + $promise->resolve($result); + return; + } + } + + $promise->reject("Unable to find station '$opt{query}'"); + return; + } + )->catch( + sub { + my ($err) = @_; + $promise->reject("'$err' while trying to look up '$opt{query}'"); + return; + } + )->wait; + + return $promise; +} + +sub get_departures_p { + my ( $self, %opt ) = @_; + + $opt{service} //= 'transitous'; + + my $timestamp = ( + $opt{timestamp} + ? $opt{timestamp}->clone + : DateTime->now + )->subtract( minutes => $opt{lookbehind} ); + + return Travel::Status::MOTIS->new_p( + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + lwp_options => { + timeout => 10, + agent => $self->{header}{'User-Agent'}, + }, + + service => $opt{service}, + timestamp => $timestamp, + stop_id => $opt{station_id}, + results => 60, + ); +} + +sub get_trip_p { + my ( $self, %opt ) = @_; + + $opt{service} //= 'transitous'; + + my $promise = Mojo::Promise->new; + + Travel::Status::MOTIS->new_p( + with_polyline => $opt{with_polyline}, + cache => $self->{realtime_cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + + service => $opt{service}, + trip_id => $opt{trip_id}, + )->then( + sub { + my ($motis) = @_; + my $journey = $motis->result; + + if ($journey) { + $self->{log}->debug("get_trip_p($opt{trip_id}): success"); + $promise->resolve($journey); + return; + } + + $self->{log}->debug("get_trip_p($opt{trip_id}): no journey"); + $promise->reject('no journey'); + return; + } + )->catch( + sub { + my ($err) = @_; + $self->{log}->debug("get_trip_p($opt{trip_id}): error $err"); + $promise->reject($err); + return; + } + )->wait; + + return $promise; +} + +1; diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index eeb1d87..0e3fdc6 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -1,6 +1,7 @@ package Travelynx::Model::InTransit; # Copyright (C) 2020-2025 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -99,6 +100,7 @@ sub add { my $train_suffix = $opt{train_suffix}; my $journey = $opt{journey}; my $stop = $opt{stop}; + my $stopover = $opt{stopover}; my $checkin_station_id = $opt{departure_eva}; my $route = $opt{route}; my $data = $opt{data}; @@ -282,6 +284,57 @@ sub add { } ); } + elsif ( $journey and $stopover ) { + + # MOTIS + my @route; + for my $journey_stopover ( $journey->stopovers ) { + push( + @route, + [ + $journey_stopover->stop->name, + $journey_stopover->stop->{eva} // die('eva not set for stopover'), + { + sched_arr => _epoch( $journey_stopover->scheduled_arrival ), + sched_dep => _epoch( $journey_stopover->scheduled_departure ), + rt_arr => _epoch( $journey_stopover->realtime_arrival ), + rt_dep => _epoch( $journey_stopover->realtime_departure ), + arr_delay => $journey_stopover->arrival_delay, + dep_delay => $journey_stopover->departure_delay, + lat => $journey_stopover->stop->lat, + lon => $journey_stopover->stop->lon, + } + ] + ); + } + + $db->insert( + 'in_transit', + { + user_id => $uid, + cancelled => $stopover->{is_cancelled} + ? 1 + : 0, + checkin_station_id => $stopover->stop->{eva}, + checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ), + dep_platform => $stopover->track, + train_type => $journey->mode, + train_no => q{}, + train_id => $journey->id, + train_line => $journey->route_name, + sched_departure => $stopover->scheduled_departure, + real_departure => $stopover->departure, + route => $json->encode( \@route ), + data => JSON->new->encode( + { + rt => $stopover->{is_realtime} ? 1 : 0, + %{ $data // {} } + } + ), + backend_id => $backend_id, + } + ); + } else { die('neither train nor journey specified'); } @@ -331,7 +384,7 @@ sub postprocess { # Note that the departure stop may be present more than once in @route, # e.g. when traveling along ring lines such as S41 / S42 in Berlin. if ( - $ret->{dep_name} + $ret->{dep_name} and $station->[0] eq $ret->{dep_name} and not($station->[2]{sched_dep} and $station->[2]{sched_dep} < $ret->{sched_dep_ts} ) @@ -887,6 +940,33 @@ sub update_departure_dbris { ); } +sub update_departure_motis { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stopover = $opt{stopover}; + my $json = JSON->new; + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_departure => $stopover->{realtime_departure}, + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + sub update_departure_hafas { my ( $self, %opt ) = @_; my $uid = $opt{uid}; @@ -1053,6 +1133,55 @@ sub update_arrival_dbris { ); } +sub update_arrival_motis { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stopover = $opt{stopover}; + my $json = JSON->new; + + my @route; + for my $journey_stopover ( $journey->stopovers ) { + push( + @route, + [ + $journey_stopover->stop->name, + $journey_stopover->stop->{eva} // die('eva not set for stopover'), + { + sched_arr => _epoch( $journey_stopover->scheduled_arrival ), + sched_dep => _epoch( $journey_stopover->scheduled_departure ), + rt_arr => _epoch( $journey_stopover->realtime_arrival ), + rt_dep => _epoch( $journey_stopover->realtime_departure ), + arr_delay => $journey_stopover->arrival_delay, + dep_delay => $journey_stopover->departure_delay, + lat => $journey_stopover->stop->lat, + lon => $journey_stopover->stop->lon, + } + ] + ); + } + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_arrival => $stopover->{realtime_arrival}, + route => $json->encode( [@route] ), + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + sub update_arrival_hafas { my ( $self, %opt ) = @_; my $uid = $opt{uid}; diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index f5bc9f1..fff59f9 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -549,7 +549,7 @@ sub get { my @select = ( - qw(journey_id is_dbris is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) + qw(journey_id is_dbris is_iris is_hafas is_motis backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) ); my %where = ( user_id => $uid, @@ -610,6 +610,7 @@ sub get { is_dbris => $entry->{is_dbris}, is_iris => $entry->{is_iris}, is_hafas => $entry->{is_hafas}, + is_motis => $entry->{is_motis}, backend_name => $entry->{backend_name}, backend_id => $entry->{backend_id}, type => $entry->{train_type}, @@ -871,8 +872,9 @@ sub get_latest_checkout_stations { my $res = $db->select( 'journeys_str', [ - 'arr_name', 'arr_eva', 'train_id', 'backend_id', - 'backend_name', 'is_dbris', 'is_hafas' + 'arr_name', 'arr_eva', 'arr_external_id', 'train_id', + 'backend_id', 'backend_name', 'is_dbris', 'is_hafas', + 'is_motis' ], { user_id => $uid, @@ -894,11 +896,13 @@ sub get_latest_checkout_stations { push( @ret, { - name => $row->{arr_name}, - eva => $row->{arr_eva}, - dbris => $row->{is_dbris} ? $row->{backend_name} : 0, - hafas => $row->{is_hafas} ? $row->{backend_name} : 0, - backend_id => $row->{backend_id}, + name => $row->{arr_name}, + eva => $row->{arr_eva}, + external_id_or_eva => $row->{arr_external_id} // $row->{arr_eva}, + dbris => $row->{is_dbris} ? $row->{backend_name} : 0, + hafas => $row->{is_hafas} ? $row->{backend_name} : 0, + motis => $row->{is_motis} ? $row->{backend_name} : 0, + backend_id => $row->{backend_id}, } ); } @@ -1392,7 +1396,7 @@ sub compute_review { if ( not $most_undelay or $speedup > ( - $most_undelay->{sched_duration} + $most_undelay->{sched_duration} - $most_undelay->{rt_duration} ) ) @@ -1665,7 +1669,7 @@ sub compute_stats { @inconsistencies, { conflict => { - train => $journey->{type} . ' ' + train => ( $journey->{is_motis} ? '' : $journey->{type} ) . ' ' . ( $journey->{line} // $journey->{no} ), arr => epoch_to_dt( $journey->{rt_arr_ts} ) ->strftime('%d.%m.%Y %H:%M'), @@ -1691,7 +1695,7 @@ sub compute_stats { $next_departure = $journey->{rt_dep_ts}; $next_id = $journey->{id}; $next_train - = $journey->{type} . ' ' . ( $journey->{line} // $journey->{no} ),; + = ( $journey->{is_motis} ? '' : $journey->{type} ) . ' ' . ( $journey->{line} // $journey->{no} ),; } my $ret = { km_route => $km_route, @@ -1740,7 +1744,7 @@ sub get_stats { # checks out of a train or manually edits/adds a journey. if ( - not $opt{write_only} + not $opt{write_only} and not $opt{review} and my $stats = $self->stats_cache->get( uid => $uid, diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index 3d6549f..761c5de 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -1,6 +1,7 @@ package Travelynx::Model::Stations; # Copyright (C) 2022 Birte Kristina Friesel +# Copyright (C) 2025 networkException # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -28,6 +29,9 @@ sub get_backend_id { if ( $opt{dbris} and $self->{backend_id}{dbris}{ $opt{dbris} } ) { return $self->{backend_id}{dbris}{ $opt{dbris} }; } + if ( $opt{motis} and $self->{backend_id}{motis}{ $opt{motis} } ) { + return $self->{backend_id}{motis}{ $opt{motis} }; + } my $db = $opt{db} // $self->{pg}->db; my $backend_id = 0; @@ -54,6 +58,17 @@ sub get_backend_id { )->hash->{id}; $self->{backend_id}{hafas}{ $opt{hafas} } = $backend_id; } + elsif ( $opt{motis} ) { + $backend_id = $db->select( + 'backends', + ['id'], + { + motis => 1, + name => $opt{motis} + } + )->hash->{id}; + $self->{backend_id}{motis}{ $opt{motis} } = $backend_id; + } return $backend_id; } @@ -85,7 +100,7 @@ sub get_backends { $opt{db} //= $self->{pg}->db; my $res = $opt{db} - ->select( 'backends', [ 'id', 'name', 'iris', 'hafas', 'dbris' ] ); + ->select( 'backends', [ 'id', 'name', 'iris', 'hafas', 'dbris', 'motis' ] ); my @ret; while ( my $row = $res->hash ) { @@ -97,6 +112,7 @@ sub get_backends { iris => $row->{iris}, dbris => $row->{dbris}, hafas => $row->{hafas}, + motis => $row->{motis}, } ); } @@ -149,6 +165,61 @@ sub add_or_update { return; } + if ( $opt{motis} ) { + if ( + my $s = $self->get_by_external_id( + external_id => $stop->id, + db => $opt{db}, + backend_id => $opt{backend_id} + ) + ) + { + $opt{db}->update( + 'stations', + { + name => $stop->name, + lat => $stop->lat, + lon => $stop->lon, + archived => 0 + }, + { + eva => $s->{eva}, + source => $opt{backend_id} + } + ); + + $stop->{eva} = $s->{eva}; + + return; + } + + my $s = $opt{db}->query( + qq { + with new_station as ( + insert into stations_external_ids (backend_id, external_id) + values (?, ?) + returning eva, backend_id + ) + + insert into stations (eva, name, lat, lon, source, archived) + values ((select eva from new_station), ?, ?, ?, (select backend_id from new_station), ?) + returning * + }, + ( + $opt{backend_id}, + $stop->id, + $stop->name, + $stop->lat, + $stop->lon, + 0, + ) + ); + + $stop->{eva} = $s->hash->{eva}; + + return; + } + my $loc = $stop->loc; if ( my $s = $self->get_by_eva( @@ -184,6 +255,8 @@ sub add_or_update { archived => 0 } ); + + return; } sub add_meta { @@ -276,6 +349,27 @@ sub get_by_eva { )->hash; } +# Slow +sub get_by_external_id { + my ( $self, %opt ) = @_; + + if ( not $opt{external_id} ) { + return; + } + + $opt{db} //= $self->{pg}->db; + $opt{backend_id} //= $self->get_backend_id(%opt); + + return $opt{db}->select( + 'stations_with_external_ids', + '*', + { + external_id => $opt{external_id}, + source => $opt{backend_id}, + } + )->hash; +} + # Fast sub get_by_evas { my ( $self, %opt ) = @_; diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 750e889..10ab17e 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -418,7 +418,7 @@ sub get { . 'extract(epoch from registered_at) as registered_at_ts, ' . 'extract(epoch from last_seen) as last_seen_ts, ' . 'extract(epoch from deletion_requested) as deletion_requested_ts, ' - . 'backend_id, backend_name, hafas, dbris', + . 'backend_id, backend_name, hafas, dbris, motis', { id => $uid } )->hash; if ($user) { @@ -459,6 +459,7 @@ sub get { backend_name => $user->{backend_name}, backend_dbris => $user->{dbris}, backend_hafas => $user->{hafas}, + backend_motis => $user->{motis}, }; } return undef; @@ -1026,11 +1027,11 @@ sub get_followers { id => $row->{id}, name => $row->{name}, following_back => ( - $row->{inverse_predicate} + $row->{inverse_predicate} and $row->{inverse_predicate} == $predicate_atoi{follows} ) ? 1 : 0, followback_requested => ( - $row->{inverse_predicate} + $row->{inverse_predicate} and $row->{inverse_predicate} == $predicate_atoi{requests_follow} ) ? 1 : 0, @@ -1102,7 +1103,7 @@ sub get_followees { id => $row->{id}, name => $row->{name}, following_back => ( - $row->{inverse_predicate} + $row->{inverse_predicate} and $row->{inverse_predicate} == $predicate_atoi{follows} ) ? 1 : 0, } diff --git a/public/static/js/geolocation.js b/public/static/js/geolocation.js index c428acd..1bb4b2b 100644 --- a/public/static/js/geolocation.js +++ b/public/static/js/geolocation.js @@ -24,7 +24,9 @@ $(document).ready(function() { const res = $(document.createElement('p')); $.each(stops, function(i, stop) { const parts = stop.split(';'); - const node = $('' + parts[1] + ''); + const [ eva, name, dbris, motis, hafas ] = parts; + + const node = $('' + name + ''); node.click(function() { $('nav .preloader-wrapper').addClass('active'); }); @@ -45,13 +47,21 @@ $(document).ready(function() { } else { const res = $(document.createElement('p')); $.each(data.candidates, function(i, candidate) { + let node; + + if (candidate.motis !== undefined) { + const { id, name, motis } = candidate; + + node = $('' + name + ''); + } else { + const eva = candidate.eva, + name = candidate.name, + hafas = candidate.hafas, + distance = candidate.distance.toFixed(1); - const eva = candidate.eva, - name = candidate.name, - hafas = candidate.hafas, - distance = candidate.distance.toFixed(1); + node = $('' + name + ''); + } - const node = $('' + name + ''); node.click(function() { $('nav .preloader-wrapper').addClass('active'); }); diff --git a/public/static/js/travelynx-actions.js b/public/static/js/travelynx-actions.js index 3e02283..d2316af 100644 --- a/public/static/js/travelynx-actions.js +++ b/public/static/js/travelynx-actions.js @@ -196,6 +196,7 @@ function tvly_reg_handlers() { action: 'checkin', dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), train: link.data('train'), suffix: link.data('suffix'), @@ -210,6 +211,7 @@ function tvly_reg_handlers() { action: 'checkout', dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), force: link.data('force'), }; @@ -242,6 +244,7 @@ function tvly_reg_handlers() { action: 'cancelled_from', dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), ts: link.data('ts'), train: link.data('train'), @@ -254,6 +257,7 @@ function tvly_reg_handlers() { action: 'cancelled_to', dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), force: true, }; @@ -320,18 +324,18 @@ $(document).ready(function() { $('nav .preloader-wrapper').addClass('active'); }); $('a[href="#now"]').keydown(function(event) { - // also trigger click handler on keyboard enter - if (event.keyCode == 13) { - event.preventDefault(); - event.target.click(); - } + // also trigger click handler on keyboard enter + if (event.keyCode == 13) { + event.preventDefault(); + event.target.click(); + } }); $('a[href="#now"]').click(function(event) { - event.preventDefault(); - $('nav .preloader-wrapper').removeClass('active'); - now_el = $('#now')[0]; - now_el.previousElementSibling.querySelector(".dep-time").focus(); - now_el.scrollIntoView({behavior: "smooth", block: "center"}); + event.preventDefault(); + $('nav .preloader-wrapper').removeClass('active'); + now_el = $('#now')[0]; + now_el.previousElementSibling.querySelector(".dep-time").focus(); + now_el.scrollIntoView({behavior: "smooth", block: "center"}); }); const elems = document.querySelectorAll('.carousel'); const instances = M.Carousel.init(elems, { diff --git a/sass/src/common/local.scss b/sass/src/common/local.scss index 605ca76..2ba0ffa 100644 --- a/sass/src/common/local.scss +++ b/sass/src/common/local.scss @@ -209,30 +209,30 @@ ul.route-history > li { min-width: 6ch; margin: 0 auto; - &.Bus, &.RUF, &.AST { + &.Bus, &.BUS, &.RUF, &.AST { background-color: #a3167e; border-radius: 5rem; padding: .2rem .5rem; } - &.STR, &.Tram, &.Str, &.Strb, &.STB { + &.STR, &.Tram, &.TRAM, &.Str, &.Strb, &.STB { background-color: #c5161c; border-radius: 5rem; padding: .2rem .5rem; } - &.S, &.RS, &.RER, &.SKW { + &.S, &.RS, &.RER, &.SKW, &.METRO { background-color: #008d4f; border-radius: 5rem; padding: .2rem .5rem; } - &.U, &.M { + &.U, &.M, &.SUBWAY { background-color: #014e8d; border-radius: 5rem; padding: .2rem .5rem; } - &.RE, &.IRE, &.REX { + &.RE, &.IRE, &.REX, &.REGIONAL_FAST_RAIL { background-color: #ff4f00; } - &.RB, &.MEX, &.TER, &.R { + &.RB, &.MEX, &.TER, &.R, &.REGIONAL_RAIL { background-color: #1f4a87; } // DE @@ -242,7 +242,9 @@ ul.route-history > li { // FR &.TGV, &.OGV, &.EST, // PL - &.TLK, &.EIC { + &.TLK, &.EIC, + // MOTIS + &.HIGHSPEED_RAIL, &.LONG_DISTANCE { background-color: #ff0404; font-weight: 900; font-style: italic; @@ -251,7 +253,7 @@ ul.route-history > li { &.RJ, &.RJX { background-color: #c63131; } - &.NJ, &.EN { + &.NJ, &.EN, &.NIGHT_RAIL { background-color: #29255b; } &.WB { diff --git a/templates/_backend_line.html.ep b/templates/_backend_line.html.ep index 5f2bcf1..00496d3 100644 --- a/templates/_backend_line.html.ep +++ b/templates/_backend_line.html.ep @@ -6,7 +6,7 @@ % } % if ($backend->{has_area}) {
- <%= join(q{, }, @{$backend->{regions} // []}) || '[Karte]' %> + <%= join(q{, }, @{$backend->{regions} // []}) || '[Karte]' %> % } % elsif ($backend->{regions}) {
diff --git a/templates/_departures_motis.html.ep b/templates/_departures_motis.html.ep new file mode 100644 index 0000000..2ebc5de --- /dev/null +++ b/templates/_departures_motis.html.ep @@ -0,0 +1,54 @@ + diff --git a/templates/_format_train.html.ep b/templates/_format_train.html.ep index 1d6acaa..e82f3f9 100644 --- a/templates/_format_train.html.ep +++ b/templates/_format_train.html.ep @@ -2,7 +2,9 @@ 🏳️‍🌈 % } - <%= $journey->{train_type} %> + % if (not $journey->{is_motis}) { + <%= $journey->{train_type} %> + % } <%= $journey->{train_line} // $journey->{train_no}%> % if ($journey->{train_line}) { diff --git a/templates/_history_trains.html.ep b/templates/_history_trains.html.ep index cf998ab..7ae2a1d 100644 --- a/templates/_history_trains.html.ep +++ b/templates/_history_trains.html.ep @@ -17,7 +17,10 @@
  • - <%= $travel->{type} %> <%= $travel->{line} // $travel->{no}%> + % if (not $travel->{is_motis}) { + <%= $travel->{type} %> + % } + <%= $travel->{line} // $travel->{no}%> diff --git a/templates/changelog.html.ep b/templates/changelog.html.ep index 7a1417f..73eae7b 100644 --- a/templates/changelog.html.ep +++ b/templates/changelog.html.ep @@ -1,5 +1,19 @@

    Changelog

    +
    +
    + 2.13 +
    +
    +

    + add + Experimentelle Unterstützung für Checkins via MOTIS-Backends + (derzeit transitous und RNV). Vielen Dank an networkException + für die Implementierung der API und Einbindung in travelynx. +

    +
    +
    +
    2.12 diff --git a/templates/departures.html.ep b/templates/departures.html.ep index bbae40f..1745a47 100644 --- a/templates/departures.html.ep +++ b/templates/departures.html.ep @@ -15,6 +15,9 @@ % elsif (param('hafas')) { <%= param('hafas') %> % } + % elsif (param('motis')) { + <%= param('motis') %> + % } % else { % if ($user->{backend_id}) { <%= $user->{backend_name} %> @@ -33,7 +36,13 @@
    Aktuell eingecheckt -

    In <%= $user_status->{train_type} %> <%= $user_status->{train_no} %> +

    In + % if ( not $user_status->{is_motis} ) { + <%= $user_status->{train_type} %> + % } + + <%= $user_status->{train_line} // $user_status->{train_no} %> + % if ( $user_status->{arr_name}) { von <%= $user_status->{dep_name} %> nach <%= $user_status->{arr_name} %> % } @@ -96,7 +105,7 @@

    - % if ($dbris or $hafas) { + % if ($dbris or $hafas or $motis) { früher % }
    @@ -106,7 +115,7 @@ % }
    - % if ($dbris or $hafas) { + % if ($dbris or $hafas or $motis) { später % }
    @@ -154,6 +163,9 @@ % elsif ($hafas) { %= include '_departures_hafas', results => $results, hafas => $hafas; % } + % elsif ($motis) { + %= include '_departures_motis', results => $results, motis => $motis; + % } % else { %= include '_departures_iris', results => $results; % } diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep index 67ba806..56aa8ff 100644 --- a/templates/landingpage.html.ep +++ b/templates/landingpage.html.ep @@ -57,7 +57,7 @@
    Hallo, <%= $user->{name} %>!

    Du bist gerade nicht eingecheckt.

    -
    + %= hidden_field backend_dbris => $user->{backend_dbris} -- cgit v1.2.3 From 0d3e16487861ca644920b7bacf1db0bac3c63199 Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Mon, 9 Jun 2025 13:31:43 +0200 Subject: perltidy --- lib/Travelynx.pm | 38 +++++++++++++++++++++------------ lib/Travelynx/Command/database.pm | 3 ++- lib/Travelynx/Command/traewelling.pm | 4 ++-- lib/Travelynx/Command/work.pm | 12 +++++++---- lib/Travelynx/Controller/Account.pm | 13 ++++++----- lib/Travelynx/Controller/Traewelling.pm | 2 +- lib/Travelynx/Controller/Traveling.pm | 18 +++++++++------- lib/Travelynx/Helper/DBDB.pm | 21 ++++++++++-------- lib/Travelynx/Helper/MOTIS.pm | 20 ++++++++--------- lib/Travelynx/Helper/Traewelling.pm | 18 +++++++++------- lib/Travelynx/Model/InTransit.pm | 34 +++++++++++++++++------------ lib/Travelynx/Model/Journeys.pm | 29 +++++++++++++++---------- lib/Travelynx/Model/Stations.pm | 12 ++++------- lib/Travelynx/Model/Users.pm | 6 +++--- 14 files changed, 130 insertions(+), 100 deletions(-) (limited to 'lib/Travelynx/Command/database.pm') diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 0429d5e..529f7fe 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -601,14 +601,19 @@ sub startup { # Lines may serve the same stop several times. # Keep looking until the scheduled departure # matches the one passed while checking in. - if ( $ts and $stopover->scheduled_departure->epoch == $ts ) { + if ( $ts + and $stopover->scheduled_departure->epoch + == $ts ) + { last; } } } if ( not $found_stopover ) { - $promise->reject("Did not find stopover at '$station' within trip '$train_id'"); + $promise->reject( +"Did not find stopover at '$station' within trip '$train_id'" + ); return; } @@ -640,7 +645,8 @@ sub startup { }; if ($@) { - $self->app->log->error("Checkin($uid): INSERT failed: $@"); + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); $promise->reject( 'INSERT failed: ' . $@ ); return; } @@ -652,21 +658,25 @@ sub startup { for my $coordinate ( $trip->polyline ) { if ( $coordinate->{stop} ) { if ( not defined $coordinate->{stop}->{eva} ) { - die() + die(); } push( @coordinate_list, [ - $coordinate->{lon}, $coordinate->{lat}, + $coordinate->{lon}, + $coordinate->{lat}, $coordinate->{stop}->{eva} ] ); - push( @station_list, $coordinate->{stop}->name ); + push( @station_list, + $coordinate->{stop}->name ); } else { - push( @coordinate_list, [ $coordinate->{lon}, $coordinate->{lat} ] ); + push( @coordinate_list, + [ $coordinate->{lon}, $coordinate->{lat} ] + ); } } @@ -680,9 +690,10 @@ sub startup { } else { $polyline = { - from_eva => ( $trip->stopovers )[0]->stop->{eva}, - to_eva => ( $trip->stopovers )[-1]->stop->{eva}, - coords => \@coordinate_list, + from_eva => + ( $trip->stopovers )[0]->stop->{eva}, + to_eva => ( $trip->stopovers )[-1]->stop->{eva}, + coords => \@coordinate_list, }; } } @@ -2267,7 +2278,7 @@ sub startup { my $ret = { deprecated => \0, checkedIn => ( - $status->{checked_in} + $status->{checked_in} or $status->{cancelled} ) ? \1 : \0, comment => $status->{comment}, @@ -2572,7 +2583,6 @@ sub startup { if ( $seen{$key} ) { next; } - $seen{$key} = 1; # direction does not matter at the moment @@ -2700,8 +2710,8 @@ sub startup { color => '#673ab7', opacity => @polylines ? $with_polyline - ? 0.4 - : 0.6 + ? 0.4 + : 0.6 : 0.8, }, { diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 0e87b2a..e264c89 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -3270,7 +3270,8 @@ sub sync_backends_motis { } } - $db->update( 'schema_version', { motis => $Travel::Status::MOTIS::VERSION } ); + $db->update( 'schema_version', + { motis => $Travel::Status::MOTIS::VERSION } ); } sub setup_db { diff --git a/lib/Travelynx/Command/traewelling.pm b/lib/Travelynx/Command/traewelling.pm index ed40371..e4e0134 100644 --- a/lib/Travelynx/Command/traewelling.pm +++ b/lib/Travelynx/Command/traewelling.pm @@ -122,12 +122,12 @@ sub push_sync { my ($status) = @_; $push_result{ $status->{http} } += 1; } - )->catch( + )->catch( sub { my ($status) = @_; $push_result{ $status->{http} // 0 } += 1; } - )->wait; + )->wait; } return \%push_result; diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 4ff5c9e..9cc0ca1 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -197,10 +197,11 @@ sub run { for my $stopover ( $journey->stopovers ) { if ( not defined $stopover->stop->{eva} ) { - my $stop = $self->app->stations->get_by_external_id( + my $stop + = $self->app->stations->get_by_external_id( external_id => $stopover->stop->id, motis => $entry->{backend_name}, - ); + ); $stopover->stop->{eva} = $stop->{eva}; } @@ -220,7 +221,8 @@ sub run { } if ( not $found_departure ) { - $self->app->log->debug("Did not find $dep within trip $train_id"); + $self->app->log->debug( + "Did not find $dep within trip $train_id"); return; } @@ -235,7 +237,9 @@ sub run { ); } - if ( $found_arrival and $found_arrival->realtime_arrival ) { + if ( $found_arrival + and $found_arrival->realtime_arrival ) + { $self->app->in_transit->update_arrival_motis( uid => $uid, journey => $journey, diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 4c69f91..db0bc61 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -1145,38 +1145,37 @@ sub backend_form { $backend->{longname} = $s->{name}; $backend->{homepage} = $s->{homepage}; $backend->{regions} = [ map { $place_map{$_} // $_ } - @{ $s->{coverage}{regions} // [] } ]; + @{ $s->{coverage}{regions} // [] } ]; $backend->{has_area} = $s->{coverage}{area} ? 1 : 0; if ( $backend->{name} eq 'transitous' ) { - $backend->{regions} = [ 'Weltweit' ]; + $backend->{regions} = ['Weltweit']; } if ( $backend->{name} eq 'RNV' ) { $backend->{homepage} = 'https://rnv-online.de/'; } if ( - $s->{coverage}{area} + $s->{coverage}{area} and $s->{coverage}{area}{type} eq 'Polygon' and $self->lonlat_in_polygon( $s->{coverage}{area}{coordinates}, [ $user_lon, $user_lat ] ) - ) + ) { push( @suggested_backends, $backend ); } elsif ( $s->{coverage}{area} and $s->{coverage}{area}{type} eq 'MultiPolygon' ) { - for my $s_poly ( - @{ $s->{coverage}{area}{coordinates} // [] } ) + for my $s_poly ( @{ $s->{coverage}{area}{coordinates} // [] } ) { if ( $self->lonlat_in_polygon( $s_poly, [ $user_lon, $user_lat ] ) - ) + ) { push( @suggested_backends, $backend ); last; diff --git a/lib/Travelynx/Controller/Traewelling.pm b/lib/Travelynx/Controller/Traewelling.pm index 3cdeff8..6aa789c 100644 --- a/lib/Travelynx/Controller/Traewelling.pm +++ b/lib/Travelynx/Controller/Traewelling.pm @@ -29,7 +29,7 @@ sub oauth { redirect_uri => $self->base_url_for('/oauth/traewelling')->to_abs->scheme( $self->app->mode eq 'development' ? 'http' : 'https' - )->to_string, + )->to_string, scope => 'read-statuses write-statuses' } )->then( diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index aa7ee9b..cb78617 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -415,7 +415,7 @@ sub homepage { } else { @recent_targets = uniq_by { $_->{external_id_or_eva} } - $self->journeys->get_latest_checkout_stations( uid => $uid ); + $self->journeys->get_latest_checkout_stations( uid => $uid ); } $self->render( 'landingpage', @@ -667,8 +667,8 @@ sub geolocation { $self->render_later; Travel::Status::MOTIS->new_p( - promise => 'Mojo::Promise', - user_agent => $self->ua, + promise => 'Mojo::Promise', + user_agent => $self->ua, service => $motis_service, stops_by_coordinate => { @@ -1148,7 +1148,7 @@ sub station { timestamp => $timestamp, lookbehind => 30, lookahead => 30, - ) + ); } else { $promise = $self->iris->get_departures_p( @@ -1206,11 +1206,13 @@ sub station { elsif ($motis_service) { @results = map { $_->[0] } sort { $b->[1] <=> $a->[1] } - map { [ $_, $_->stopover->departure->epoch ] } $status->results; + map { [ $_, $_->stopover->departure->epoch ] } + $status->results; $status = { - station_eva => $station, - station_name => $status->{results}->[0]->stopover->stop->name, + station_eva => $station, + station_name => + $status->{results}->[0]->stopover->stop->name, related_stations => [], }; } @@ -2020,7 +2022,7 @@ sub journey_details { $delay = sprintf( 'mit %+d ', ( - $journey->{rt_arrival}->epoch + $journey->{rt_arrival}->epoch - $journey->{sched_arrival}->epoch ) / 60 ); diff --git a/lib/Travelynx/Helper/DBDB.pm b/lib/Travelynx/Helper/DBDB.pm index 79e9c0b..a310aa3 100644 --- a/lib/Travelynx/Helper/DBDB.pm +++ b/lib/Travelynx/Helper/DBDB.pm @@ -61,7 +61,8 @@ sub has_wagonorder_p { } } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) + $self->{user_agent}->request_timeout(5) + ->get_p( $url => $self->{header} ) ->then( sub { my ($tx) = @_; @@ -81,7 +82,7 @@ sub has_wagonorder_p { } return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("${debug_prefix}: n ($err)"); @@ -89,7 +90,7 @@ sub has_wagonorder_p { $promise->reject; return; } - )->wait; + )->wait; return $promise; } @@ -120,7 +121,8 @@ sub get_wagonorder_p { return $promise; } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) + $self->{user_agent}->request_timeout(5) + ->get_p( $url => $self->{header} ) ->then( sub { my ($tx) = @_; @@ -139,14 +141,14 @@ sub get_wagonorder_p { } return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("${debug_prefix}: error ${err}"); $promise->reject($err); return; } - )->wait; + )->wait; return $promise; } @@ -163,7 +165,8 @@ sub get_stationinfo_p { return $promise->resolve($content); } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) + $self->{user_agent}->request_timeout(5) + ->get_p( $url => $self->{header} ) ->then( sub { my ($tx) = @_; @@ -183,7 +186,7 @@ sub get_stationinfo_p { $promise->resolve($json); return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("get_stationinfo_p(${eva}): Error ${err}"); @@ -191,7 +194,7 @@ sub get_stationinfo_p { $promise->reject($err); return; } - )->wait; + )->wait; return $promise; } diff --git a/lib/Travelynx/Helper/MOTIS.pm b/lib/Travelynx/Helper/MOTIS.pm index ee2b10b..d4e1777 100644 --- a/lib/Travelynx/Helper/MOTIS.pm +++ b/lib/Travelynx/Helper/MOTIS.pm @@ -50,10 +50,10 @@ sub get_station_by_query_p { my $promise = Mojo::Promise->new; Travel::Status::MOTIS->new_p( - cache => $self->{cache}, - promise => 'Mojo::Promise', - user_agent => Mojo::UserAgent->new, - lwp_options => { + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + lwp_options => { timeout => 10, agent => $self->{header}{'User-Agent'}, }, @@ -106,10 +106,10 @@ sub get_departures_p { agent => $self->{header}{'User-Agent'}, }, - service => $opt{service}, - timestamp => $timestamp, - stop_id => $opt{station_id}, - results => 60, + service => $opt{service}, + timestamp => $timestamp, + stop_id => $opt{station_id}, + results => 60, ); } @@ -126,8 +126,8 @@ sub get_trip_p { promise => 'Mojo::Promise', user_agent => Mojo::UserAgent->new, - service => $opt{service}, - trip_id => $opt{trip_id}, + service => $opt{service}, + trip_id => $opt{trip_id}, )->then( sub { my ($motis) = @_; diff --git a/lib/Travelynx/Helper/Traewelling.pm b/lib/Travelynx/Helper/Traewelling.pm index 100a799..66f2a29 100644 --- a/lib/Travelynx/Helper/Traewelling.pm +++ b/lib/Travelynx/Helper/Traewelling.pm @@ -78,7 +78,8 @@ sub get_status_p { $self->{user_agent}->request_timeout(20) ->get_p( "https://traewelling.de/api/v1/user/${username}/statuses?limit=1" => - $header )->then( + $header ) + ->then( sub { my ($tx) = @_; if ( my $err = $tx->error ) { @@ -150,13 +151,13 @@ sub get_status_p { } } } - )->catch( + )->catch( sub { my ($err) = @_; $promise->reject( { text => "v1/${username}/statuses: $err" } ); return; } - )->wait; + )->wait; return $promise; } @@ -238,13 +239,13 @@ sub logout_p { return; } } - )->catch( + )->catch( sub { my ($err) = @_; $promise->reject("v1/auth/logout: $err"); return; } - )->wait; + )->wait; return $promise; } @@ -322,7 +323,8 @@ sub checkin_p { $self->{user_agent}->request_timeout(20) ->post_p( "https://traewelling.de/api/v1/trains/checkin" => $header => json => - $request )->then( + $request ) + ->then( sub { my ($tx) = @_; if ( my $err = $tx->error ) { @@ -368,7 +370,7 @@ sub checkin_p { # on the user status page return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("... $debug_prefix error: $err"); @@ -381,7 +383,7 @@ sub checkin_p { $promise->reject( { connection => $err } ); return; } - )->wait; + )->wait; return $promise; } diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index 0e3fdc6..cc943b3 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -293,16 +293,20 @@ sub add { @route, [ $journey_stopover->stop->name, - $journey_stopover->stop->{eva} // die('eva not set for stopover'), + $journey_stopover->stop->{eva} + // die('eva not set for stopover'), { - sched_arr => _epoch( $journey_stopover->scheduled_arrival ), - sched_dep => _epoch( $journey_stopover->scheduled_departure ), - rt_arr => _epoch( $journey_stopover->realtime_arrival ), - rt_dep => _epoch( $journey_stopover->realtime_departure ), + sched_arr => + _epoch( $journey_stopover->scheduled_arrival ), + sched_dep => + _epoch( $journey_stopover->scheduled_departure ), + rt_arr => _epoch( $journey_stopover->realtime_arrival ), + rt_dep => + _epoch( $journey_stopover->realtime_departure ), arr_delay => $journey_stopover->arrival_delay, dep_delay => $journey_stopover->departure_delay, - lat => $journey_stopover->stop->lat, - lon => $journey_stopover->stop->lon, + lat => $journey_stopover->stop->lat, + lon => $journey_stopover->stop->lon, } ] ); @@ -384,7 +388,7 @@ sub postprocess { # Note that the departure stop may be present more than once in @route, # e.g. when traveling along ring lines such as S41 / S42 in Berlin. if ( - $ret->{dep_name} + $ret->{dep_name} and $station->[0] eq $ret->{dep_name} and not($station->[2]{sched_dep} and $station->[2]{sched_dep} < $ret->{sched_dep_ts} ) @@ -1149,16 +1153,18 @@ sub update_arrival_motis { @route, [ $journey_stopover->stop->name, - $journey_stopover->stop->{eva} // die('eva not set for stopover'), + $journey_stopover->stop->{eva} + // die('eva not set for stopover'), { sched_arr => _epoch( $journey_stopover->scheduled_arrival ), - sched_dep => _epoch( $journey_stopover->scheduled_departure ), - rt_arr => _epoch( $journey_stopover->realtime_arrival ), - rt_dep => _epoch( $journey_stopover->realtime_departure ), + sched_dep => + _epoch( $journey_stopover->scheduled_departure ), + rt_arr => _epoch( $journey_stopover->realtime_arrival ), + rt_dep => _epoch( $journey_stopover->realtime_departure ), arr_delay => $journey_stopover->arrival_delay, dep_delay => $journey_stopover->departure_delay, - lat => $journey_stopover->stop->lat, - lon => $journey_stopover->stop->lon, + lat => $journey_stopover->stop->lat, + lon => $journey_stopover->stop->lon, } ] ); diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index fff59f9..8efbab2 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -872,8 +872,10 @@ sub get_latest_checkout_stations { my $res = $db->select( 'journeys_str', [ - 'arr_name', 'arr_eva', 'arr_external_id', 'train_id', - 'backend_id', 'backend_name', 'is_dbris', 'is_hafas', + 'arr_name', 'arr_eva', + 'arr_external_id', 'train_id', + 'backend_id', 'backend_name', + 'is_dbris', 'is_hafas', 'is_motis' ], { @@ -898,11 +900,12 @@ sub get_latest_checkout_stations { { name => $row->{arr_name}, eva => $row->{arr_eva}, - external_id_or_eva => $row->{arr_external_id} // $row->{arr_eva}, - dbris => $row->{is_dbris} ? $row->{backend_name} : 0, - hafas => $row->{is_hafas} ? $row->{backend_name} : 0, - motis => $row->{is_motis} ? $row->{backend_name} : 0, - backend_id => $row->{backend_id}, + external_id_or_eva => $row->{arr_external_id} + // $row->{arr_eva}, + dbris => $row->{is_dbris} ? $row->{backend_name} : 0, + hafas => $row->{is_hafas} ? $row->{backend_name} : 0, + motis => $row->{is_motis} ? $row->{backend_name} : 0, + backend_id => $row->{backend_id}, } ); } @@ -1396,7 +1399,7 @@ sub compute_review { if ( not $most_undelay or $speedup > ( - $most_undelay->{sched_duration} + $most_undelay->{sched_duration} - $most_undelay->{rt_duration} ) ) @@ -1669,7 +1672,10 @@ sub compute_stats { @inconsistencies, { conflict => { - train => ( $journey->{is_motis} ? '' : $journey->{type} ) . ' ' + train => ( + $journey->{is_motis} ? '' : $journey->{type} + ) + . ' ' . ( $journey->{line} // $journey->{no} ), arr => epoch_to_dt( $journey->{rt_arr_ts} ) ->strftime('%d.%m.%Y %H:%M'), @@ -1695,7 +1701,8 @@ sub compute_stats { $next_departure = $journey->{rt_dep_ts}; $next_id = $journey->{id}; $next_train - = ( $journey->{is_motis} ? '' : $journey->{type} ) . ' ' . ( $journey->{line} // $journey->{no} ),; + = ( $journey->{is_motis} ? '' : $journey->{type} ) . ' ' + . ( $journey->{line} // $journey->{no} ),; } my $ret = { km_route => $km_route, @@ -1744,7 +1751,7 @@ sub get_stats { # checks out of a train or manually edits/adds a journey. if ( - not $opt{write_only} + not $opt{write_only} and not $opt{review} and my $stats = $self->stats_cache->get( uid => $uid, diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index 761c5de..6758b93 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -99,8 +99,8 @@ sub get_backends { $opt{db} //= $self->{pg}->db; - my $res = $opt{db} - ->select( 'backends', [ 'id', 'name', 'iris', 'hafas', 'dbris', 'motis' ] ); + my $res = $opt{db}->select( 'backends', + [ 'id', 'name', 'iris', 'hafas', 'dbris', 'motis' ] ); my @ret; while ( my $row = $res->hash ) { @@ -206,12 +206,8 @@ sub add_or_update { returning * }, ( - $opt{backend_id}, - $stop->id, - $stop->name, - $stop->lat, - $stop->lon, - 0, + $opt{backend_id}, $stop->id, $stop->name, + $stop->lat, $stop->lon, 0, ) ); diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 10ab17e..1c3692e 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -1027,11 +1027,11 @@ sub get_followers { id => $row->{id}, name => $row->{name}, following_back => ( - $row->{inverse_predicate} + $row->{inverse_predicate} and $row->{inverse_predicate} == $predicate_atoi{follows} ) ? 1 : 0, followback_requested => ( - $row->{inverse_predicate} + $row->{inverse_predicate} and $row->{inverse_predicate} == $predicate_atoi{requests_follow} ) ? 1 : 0, @@ -1103,7 +1103,7 @@ sub get_followees { id => $row->{id}, name => $row->{name}, following_back => ( - $row->{inverse_predicate} + $row->{inverse_predicate} and $row->{inverse_predicate} == $predicate_atoi{follows} ) ? 1 : 0, } -- cgit v1.2.3