diff options
Diffstat (limited to 'lib/Travelynx/Controller')
-rw-r--r-- | lib/Travelynx/Controller/Account.pm | 99 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Api.pm | 14 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Profile.pm | 28 | ||||
-rw-r--r-- | lib/Travelynx/Controller/Traewelling.pm | 2 | ||||
-rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 347 |
5 files changed, 451 insertions, 39 deletions
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 97a3cf6..74c272f 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 <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; @@ -1066,8 +1067,15 @@ 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'; + $backend->{legacy} = 1; + } + elsif ( $backend->{dbris} ) { + $type = 'DBRIS'; + $backend->{longname} = 'Deutsche Bahn: bahn.de'; + $backend->{homepage} = 'https://www.bahn.de'; + $backend->{recommended} = 1; } elsif ( $backend->{efa} ) { if ( my $s = $self->efa->get_service( $backend->{name} ) ) { @@ -1112,7 +1120,28 @@ sub backend_form { } } elsif ( $backend->{hafas} ) { - if ( my $s = $self->hafas->get_service( $backend->{name} ) ) { + + # These backends lack a journey endpoint or are no longer + # operational and are thus useless for travelynx + if ( $backend->{name} eq 'Resrobot' + or $backend->{name} eq 'TPG' + or $backend->{name} eq 'VRN' + or $backend->{name} eq 'DB' ) + { + $type = undef; + } + + # PKP is behind a GeoIP filter. Only list it if travelynx.conf + # indicates that our IP is allowed or provides a proxy. + elsif ( + $backend->{name} eq 'PKP' + and not( $self->app->config->{hafas}{PKP}{geoip_ok} + or $self->app->config->{hafas}{PKP}{proxy} ) + ) + { + $type = undef; + } + elsif ( my $s = $self->hafas->get_service( $backend->{name} ) ) { $type = 'HAFAS'; $backend->{longname} = $s->{name}; $backend->{homepage} = $s->{homepage}; @@ -1120,6 +1149,13 @@ sub backend_form { @{ $s->{coverage}{regions} // [] } ]; $backend->{has_area} = $s->{coverage}{area} ? 1 : 0; + if ( $backend->{name} eq 'ÖBB' ) { + $backend->{recommended} = 1; + } + else { + $backend->{association} = 1; + } + if ( $s->{coverage}{area} and $s->{coverage}{area}{type} eq 'Polygon' @@ -1153,19 +1189,58 @@ 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; + $backend->{experimental} = 1; + + 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; } - # 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; - - unshift( @backends, $iris ); + @backends = map { $_->[1] } + sort { $a->[0] cmp $b->[0] } + map { [ lc( $_->{name} ), $_ ] } grep { $_->{type} } @backends; $self->render( 'select_backend', diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index f31195a..572d3fa 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -21,6 +21,9 @@ sub sanitize { if ( not defined $value ) { return undef; } + if ( not defined $type ) { + return $value ? ( '' . $value ) : undef; + } if ( $type eq '' ) { return '' . $value; } @@ -184,8 +187,13 @@ sub travel_v1 { my $from_station = sanitize( q{}, $payload->{fromStation} ); my $to_station = sanitize( q{}, $payload->{toStation} ); my $train_id; + my $dbris = sanitize( undef, $payload->{dbris} ); my $hafas = sanitize( undef, $payload->{hafas} ); - $hafas //= exists $payload->{train}{journeyID} ? 'DB' : undef; + my $motis = sanitize( undef, $payload->{motis} ); + + if ( not $hafas and exists $payload->{train}{journeyID} ) { + $dbris //= 'bahn.de'; + } if ( not( @@ -209,6 +217,7 @@ sub travel_v1 { } if ( not $hafas + and not $dbris and not $self->stations->search( $from_station, backend_id => 1 ) ) { $self->render( @@ -225,6 +234,7 @@ sub travel_v1 { if ( $to_station and not $hafas + and not $dbris and not $self->stations->search( $to_station, backend_id => 1 ) ) { $self->render( @@ -288,6 +298,8 @@ sub travel_v1 { train_id => $train_id, uid => $uid, hafas => $hafas, + dbris => $dbris, + motis => $motis, ); } )->then( diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm index c35642d..db30d36 100755 --- a/lib/Travelynx/Controller/Profile.pm +++ b/lib/Travelynx/Controller/Profile.pm @@ -111,6 +111,13 @@ sub profile { $status->{arr_name} = undef; } + my $map_data = {}; + if ( $status->{checked_in} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } + my @journeys; if ( @@ -190,6 +197,8 @@ sub profile { : 0, journey => $status, journeys => [@journeys], + with_map => 1, + %{$map_data}, } ); } @@ -494,6 +503,13 @@ sub user_status { $og_data{description} = $tw_data{description} = q{}; } + my $map_data = {}; + if ( $status->{checked_in} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } + $self->respond_to( json => { json => { @@ -516,7 +532,9 @@ sub user_status { journey => $status, twitter => \%tw_data, opengraph => \%og_data, - version => $self->app->config->{version} // 'UNKNOWN', + with_map => 1, + %{$map_data}, + version => $self->app->config->{version} // 'UNKNOWN', }, ); } @@ -555,6 +573,7 @@ sub status_card { my $status = $self->get_user_status( $user->{id} ); my $visibility; + my $map_data = {}; if ( $status->{checked_in} or $status->{arr_name} ) { my $visibility = $status->{effective_visibility}; if ( @@ -579,12 +598,19 @@ sub status_card { $status->{arr_name} = undef; } + if ( $status->{checked_in} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } + $self->render( '_public_status_card', name => $name, privacy => $user, journey => $status, from_profile => $self->param('profile') ? 1 : 0, + %{$map_data}, ); } 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 53d28a9..de1a547 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 <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; @@ -10,6 +11,7 @@ use DateTime::Format::Strptime; use List::Util qw(uniq min max); use List::UtilsBy qw(max_by uniq_by); use List::MoreUtils qw(first_index); +use Mojo::UserAgent; use Mojo::Promise; use Text::CSV; use Travel::Status::DE::IRIS::Stations; @@ -41,6 +43,18 @@ 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 ( $user->{backend_motis} ) { + + # FIXME: The following code can't handle external_ids currently + return $promise->reject; + } + if ( $opt{eva} ) { if ( $use_history & 0x01 ) { $eva = $opt{eva}; @@ -105,6 +119,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, @@ -259,9 +275,11 @@ sub get_connecting_trains_p { } )->wait; } - else { - my $hafas_service - = $self->stations->get_hafas_name( backend_id => $opt{backend_id} ); + elsif ( $backend->{dbris} ) { + return $promise->reject; + } + elsif ( $backend->{hafas} ) { + my $hafas_service = $backend->{name}; $self->hafas->get_departures_p( service => $hafas_service, eva => $eva, @@ -341,6 +359,12 @@ sub homepage { $self->stash( timeline => [@timeline] ); my @recent_targets; if ( $status->{checked_in} ) { + my $map_data = {}; + if ( $status->{arr_name} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } my $journey_visibility = $self->compute_effective_visibility( $user->{default_visibility_str}, @@ -359,6 +383,8 @@ sub homepage { journey_visibility => $journey_visibility, connections_iris => $connections_iris, connections_hafas => $connections_hafas, + with_map => 1, + %{$map_data}, ); $self->users->mark_seen( uid => $uid ); } @@ -369,6 +395,8 @@ sub homepage { user => $user, user_status => $status, journey_visibility => $journey_visibility, + with_map => 1, + %{$map_data}, ); $self->users->mark_seen( uid => $uid ); } @@ -381,13 +409,15 @@ sub homepage { user => $user, user_status => $status, journey_visibility => $journey_visibility, + with_map => 1, + %{$map_data}, ); $self->users->mark_seen( uid => $uid ); return; } } else { - @recent_targets = uniq_by { $_->{eva} } + @recent_targets = uniq_by { $_->{external_id_or_eva} } $self->journeys->get_latest_checkout_stations( uid => $uid ); } $self->render( @@ -419,6 +449,12 @@ sub status_card { $self->stash( timeline => [@timeline] ); if ( $status->{checked_in} ) { + my $map_data = {}; + if ( $status->{arr_name} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } my $journey_visibility = $self->compute_effective_visibility( $self->current_user->{default_visibility_str}, @@ -436,6 +472,7 @@ sub status_card { journey_visibility => $journey_visibility, connections_iris => $connections_iris, connections_hafas => $connections_hafas, + %{$map_data}, ); } )->catch( @@ -444,6 +481,7 @@ sub status_card { '_checked_in', journey => $status, journey_visibility => $journey_visibility, + %{$map_data}, ); } )->wait; @@ -453,6 +491,7 @@ sub status_card { '_checked_in', journey => $status, journey_visibility => $journey_visibility, + %{$map_data}, ); } elsif ( $status->{cancellation} ) { @@ -523,15 +562,74 @@ sub geolocation { return; } - my $hafas_service - = $self->stations->get_hafas_name( backend_id => $backend_id ); + my ( $dbris_service, $hafas_service, $motis_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}; + } + elsif ( $backend->{motis} ) { + $motis_service = $backend->{name}; + } - if ($hafas_service) { + if ($dbris_service) { $self->render_later; + Travel::Status::DE::DBRIS->new_p( + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + geoSearch => { + latitude => $lat, + longitude => $lon + } + )->then( + sub { + my ($dbris) = @_; + my @results = map { + { + name => $_->name, + eva => $_->eva, + distance => 0, + dbris => $dbris_service, + } + } $dbris->results; + if ( @results > 10 ) { + @results = @results[ 0 .. 9 ]; + } + $self->render( + json => { + candidates => [@results], + } + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + json => { + candidates => [], + warning => $err, + } + ); + } + )->wait; + return; + } + elsif ($hafas_service) { + $self->render_later; + + my $agent = $self->ua; + if ( my $proxy = $self->app->config->{hafas}{$hafas_service}{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + Travel::Status::DE::HAFAS->new_p( promise => 'Mojo::Promise', - user_agent => $self->ua, + user_agent => $agent, service => $hafas_service, geoSearch => { lat => $lat, @@ -571,6 +669,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 { { @@ -580,6 +726,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, @@ -648,10 +795,13 @@ sub travel_action { $promise->then( sub { return $self->checkin_p( - hafas => $params->{hafas}, - station => $params->{station}, - train_id => $params->{train}, - ts => $params->{ts}, + dbris => $params->{dbris}, + hafas => $params->{hafas}, + motis => $params->{motis}, + station => $params->{station}, + train_id => $params->{train}, + train_suffix => $params->{suffix}, + ts => $params->{ts}, ); } )->then( @@ -679,7 +829,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( @@ -715,7 +868,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}; } @@ -766,13 +922,27 @@ 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} . '?hafas=' . $status->{backend_name}; } + elsif ( $status->{is_motis} ) { + $redir + = '/s/' + . $status->{dep_external_id} + . '?motis=' + . $status->{backend_name}; + } else { $redir = '/s/' . $status->{dep_ds100}; } @@ -788,7 +958,9 @@ sub travel_action { elsif ( $params->{action} eq 'cancelled_from' ) { $self->render_later; $self->checkin_p( + dbris => $params->{dbris}, hafas => $params->{hafas}, + motis => $params->{motis}, station => $params->{station}, train_id => $params->{train}, ts => $params->{ts}, @@ -917,12 +1089,38 @@ sub station { $timestamp = DateTime->now( time_zone => 'Europe/Berlin' ); } + my $dbris_service = $self->param('dbris') + // ( $user->{backend_dbris} ? $user->{backend_name} : undef ); my $efa_service = $self->param('efa') // ( $user->{backend_efa} ? $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 ($hafas_service) { + if ($dbris_service) { + if ( $station !~ m{ [@] L = \d+ }x ) { + $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; + return; + } + $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, @@ -936,7 +1134,36 @@ sub station { service => $efa_service, name => $station, timestamp => $timestamp, - lookbehind => 10, + lookbehind => 30, + 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, ); } @@ -957,7 +1184,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 = (?<name> [^@]+ ) [@] }x ) { + $status->{station_name} = $+{name}; + } + } + elsif ($hafas_service) { @results = map { $_->[0] } sort { $b->[1] <=> $a->[1] } @@ -988,6 +1230,19 @@ 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 @@ -1054,8 +1309,10 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, efa => $efa_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1074,8 +1331,10 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, efa => $efa_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1093,8 +1352,10 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, efa => $efa_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1157,18 +1418,23 @@ sub station { } )->wait; } - elsif ( $err =~ m{svcRes|connection close} ) { + elsif ( $err + =~ m{svcRes|connection close|Service Temporarily Unavailable|Forbidden} + ) + { $self->render( 'bad_gateway', - message => $err, - status => 502 + message => $err, + status => 502, + select_new_backend => 1, ); } elsif ( $err =~ m{timeout}i ) { $self->render( 'gateway_timeout', - message => $err, - status => 504 + message => $err, + status => 504, + select_new_backend => 1, ); } else { @@ -1187,7 +1453,40 @@ 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; + } + 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}"); + } } sub cancelled { |