show current/next stop while checked in
This commit is contained in:
parent
155f9f39cc
commit
b1591eed54
4 changed files with 360 additions and 4 deletions
262
lib/Travelynx.pm
262
lib/Travelynx.pm
|
@ -2,10 +2,12 @@ package Travelynx;
|
|||
use Mojo::Base 'Mojolicious';
|
||||
|
||||
use Mojo::Pg;
|
||||
use Mojo::Promise;
|
||||
use Mojolicious::Plugin::Authentication;
|
||||
use Cache::File;
|
||||
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
|
||||
use DateTime;
|
||||
use DateTime::Format::Strptime;
|
||||
use Encode qw(decode encode);
|
||||
use Geo::Distance;
|
||||
use JSON;
|
||||
|
@ -14,6 +16,7 @@ use List::MoreUtils qw(after_incl before_incl);
|
|||
use Travel::Status::DE::IRIS;
|
||||
use Travel::Status::DE::IRIS::Stations;
|
||||
use Travelynx::Helper::Sendmail;
|
||||
use XML::LibXML;
|
||||
|
||||
sub check_password {
|
||||
my ( $password, $hash ) = @_;
|
||||
|
@ -395,6 +398,8 @@ sub startup {
|
|||
"Checkin($uid): INSERT failed: $@");
|
||||
return ( undef, 'INSERT failed: ' . $@ );
|
||||
}
|
||||
$self->add_route_timestamps( $self->current_user->{id},
|
||||
$train );
|
||||
$self->run_hook( $self->current_user->{id}, 'checkin' );
|
||||
return ( $train, undef );
|
||||
}
|
||||
|
@ -630,6 +635,7 @@ sub startup {
|
|||
return ( 0, undef );
|
||||
}
|
||||
$self->run_hook( $uid, 'update' );
|
||||
$self->add_route_timestamps( $self->current_user->{id}, $train );
|
||||
return ( 1, undef );
|
||||
}
|
||||
);
|
||||
|
@ -1489,6 +1495,223 @@ sub startup {
|
|||
}
|
||||
);
|
||||
|
||||
$self->helper(
|
||||
'get_hafas_json_p' => sub {
|
||||
my ( $self, $url ) = @_;
|
||||
|
||||
my $cache = $self->app->cache_iris_main;
|
||||
my $promise = Mojo::Promise->new;
|
||||
|
||||
if ( my $content = $cache->thaw($url) ) {
|
||||
$promise->resolve($content);
|
||||
return $promise;
|
||||
}
|
||||
|
||||
$self->ua->request_timeout(5)->get_p($url)->then(
|
||||
sub {
|
||||
my ($tx) = @_;
|
||||
my $body = decode( 'ISO-8859-15', $tx->res->body );
|
||||
|
||||
$body =~ s{^TSLs[.]sls = }{};
|
||||
$body =~ s{;$}{};
|
||||
$body =~ s{(}{(}g;
|
||||
$body =~ s{)}{)}g;
|
||||
my $json = JSON->new->decode($body);
|
||||
$cache->freeze( $url, $json );
|
||||
$promise->resolve($json);
|
||||
}
|
||||
)->catch(
|
||||
sub {
|
||||
my ($err) = @_;
|
||||
$self->app->log->warning("get($url): $err");
|
||||
$promise->reject($err);
|
||||
}
|
||||
)->wait;
|
||||
return $promise;
|
||||
}
|
||||
);
|
||||
|
||||
$self->helper(
|
||||
'get_hafas_xml_p' => sub {
|
||||
my ( $self, $url ) = @_;
|
||||
|
||||
my $cache = $self->app->cache_iris_rt;
|
||||
my $promise = Mojo::Promise->new;
|
||||
|
||||
if ( my $content = $cache->thaw($url) ) {
|
||||
$promise->resolve($content);
|
||||
return $promise;
|
||||
}
|
||||
|
||||
$self->ua->request_timeout(5)->get_p($url)->then(
|
||||
sub {
|
||||
my ($tx) = @_;
|
||||
my $body = decode( 'ISO-8859-15', $tx->res->body );
|
||||
my $tree;
|
||||
|
||||
my $traininfo = {
|
||||
station => {},
|
||||
};
|
||||
|
||||
# <SDay text="... > ..."> is invalid HTML, but present in
|
||||
# regardless. As it is the last tag, we just throw it away.
|
||||
$body =~ s{<SDay .*}{</Journey>}s;
|
||||
eval { $tree = XML::LibXML->load_xml( string => $body ) };
|
||||
if ($@) {
|
||||
$self->app->log->warning("load_xml($url): $@");
|
||||
$cache->freeze( $url, $traininfo );
|
||||
$promise->resolve($traininfo);
|
||||
return;
|
||||
}
|
||||
|
||||
for my $station ( $tree->findnodes('/Journey/St') ) {
|
||||
my $name = $station->getAttribute('name');
|
||||
my $adelay = $station->getAttribute('adelay');
|
||||
my $ddelay = $station->getAttribute('ddelay');
|
||||
$traininfo->{station}{$name} = {
|
||||
adelay => $adelay,
|
||||
ddelay => $ddelay,
|
||||
};
|
||||
}
|
||||
|
||||
$cache->freeze( $url, $traininfo );
|
||||
$promise->resolve($traininfo);
|
||||
}
|
||||
)->catch(
|
||||
sub {
|
||||
my ($err) = @_;
|
||||
$self->app->log->warning("get($url): $err");
|
||||
$promise->reject($err);
|
||||
}
|
||||
)->wait;
|
||||
return $promise;
|
||||
}
|
||||
);
|
||||
|
||||
$self->helper(
|
||||
'add_route_timestamps' => sub {
|
||||
my ( $self, $uid, $train ) = @_;
|
||||
|
||||
$uid //= $self->current_user->{id};
|
||||
|
||||
my $db = $self->pg->db;
|
||||
|
||||
my $journey
|
||||
= $db->select( 'in_transit', ['route'], { user_id => $uid } )
|
||||
->expand->hash;
|
||||
|
||||
if ( not $journey ) {
|
||||
return;
|
||||
}
|
||||
|
||||
my $route = $journey->{route};
|
||||
|
||||
my $base
|
||||
= 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json.vs_hap&start=yes&rt=1';
|
||||
my $date_yy = $train->start->strftime('%d.%m.%y');
|
||||
my $date_yyyy = $train->start->strftime('%d.%m.%Y');
|
||||
my $train_no = $train->type . ' ' . $train->train_no;
|
||||
|
||||
$self->app->log->debug("add_route_timestamps");
|
||||
|
||||
my ( $trainlink, $route_data );
|
||||
|
||||
$self->get_hafas_json_p(
|
||||
"${base}&date=${date_yy}&trainname=${train_no}")->then(
|
||||
sub {
|
||||
my ($trainsearch) = @_;
|
||||
|
||||
# Fallback: Take first result
|
||||
$trainlink = $trainsearch->{suggestions}[0]{trainLink};
|
||||
|
||||
# Try finding a result for the current date
|
||||
for
|
||||
my $suggestion ( @{ $trainsearch->{suggestions} // [] } )
|
||||
{
|
||||
|
||||
# Drunken API, sail with care. Both date formats are used interchangeably
|
||||
if ( $suggestion->{depDate} eq $date_yy
|
||||
or $suggestion->{depDate} eq $date_yyyy )
|
||||
{
|
||||
$trainlink = $suggestion->{trainLink};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if ( not $trainlink ) {
|
||||
$self->app->log->debug("trainlink not found");
|
||||
return Mojo::Promise->reject("trainlink not found");
|
||||
}
|
||||
my $base2
|
||||
= 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
|
||||
return $self->get_hafas_json_p(
|
||||
"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_json.vs_hap"
|
||||
);
|
||||
}
|
||||
)->then(
|
||||
sub {
|
||||
my ($traininfo) = @_;
|
||||
|
||||
if ( not $traininfo or $traininfo->{error} ) {
|
||||
$self->app->log->debug("traininfo error");
|
||||
return Mojo::Promise->reject("traininfo error");
|
||||
}
|
||||
my $routeinfo
|
||||
= $traininfo->{suggestions}[0]{locations};
|
||||
|
||||
my $strp = DateTime::Format::Strptime->new(
|
||||
pattern => '%d.%m.%y %H:%M',
|
||||
time_zone => 'Europe/Berlin',
|
||||
);
|
||||
|
||||
$route_data = {};
|
||||
|
||||
for my $station ( @{$routeinfo} ) {
|
||||
my $arr
|
||||
= $strp->parse_datetime(
|
||||
$station->{arrDate} . ' ' . $station->{arrTime} );
|
||||
my $dep
|
||||
= $strp->parse_datetime(
|
||||
$station->{depDate} . ' ' . $station->{depTime} );
|
||||
$route_data->{ $station->{name} } = {
|
||||
sched_arr => $arr ? $arr->epoch : 0,
|
||||
sched_dep => $dep ? $dep->epoch : 0,
|
||||
};
|
||||
}
|
||||
|
||||
my $base2
|
||||
= 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
|
||||
return $self->get_hafas_xml_p(
|
||||
"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3"
|
||||
);
|
||||
}
|
||||
)->then(
|
||||
sub {
|
||||
my ($traininfo2) = @_;
|
||||
|
||||
for my $station ( keys %{$route_data} ) {
|
||||
for my $key (
|
||||
keys %{ $traininfo2->{station}{$station} // {} } )
|
||||
{
|
||||
$route_data->{$station}{$key}
|
||||
= $traininfo2->{station}{$station}{$key};
|
||||
}
|
||||
}
|
||||
|
||||
for my $station ( @{$route} ) {
|
||||
$station->[1]
|
||||
= $route_data->{ $station->[0] };
|
||||
}
|
||||
$db->update(
|
||||
'in_transit',
|
||||
{ route => JSON->new->encode($route) },
|
||||
{ user_id => $uid }
|
||||
);
|
||||
}
|
||||
)->wait;
|
||||
}
|
||||
);
|
||||
|
||||
$self->helper(
|
||||
'get_oldest_journey_ts' => sub {
|
||||
my ($self) = @_;
|
||||
|
@ -1828,6 +2051,7 @@ sub startup {
|
|||
|
||||
my $db = $self->pg->db;
|
||||
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
||||
my $epoch = $now->epoch;
|
||||
|
||||
my $in_transit
|
||||
= $db->select( 'in_transit_str', '*', { user_id => $uid } )
|
||||
|
@ -1883,12 +2107,45 @@ sub startup {
|
|||
}
|
||||
$ret->{messages} = [ reverse @parsed_messages ];
|
||||
|
||||
for my $station (@route_after) {
|
||||
if ( @{$station} > 1 ) {
|
||||
my $times = $station->[1];
|
||||
if ( $times->{sched_arr} ) {
|
||||
$times->{sched_arr}
|
||||
= epoch_to_dt( $times->{sched_arr} );
|
||||
$times->{rt_arr} = $times->{sched_arr}->clone;
|
||||
if ( $times->{adelay}
|
||||
and $times->{adelay} =~ m{^\d+$} )
|
||||
{
|
||||
$times->{rt_arr}
|
||||
->add( minutes => $times->{adelay} );
|
||||
}
|
||||
$times->{rt_arr_countdown}
|
||||
= $times->{rt_arr}->epoch - $epoch;
|
||||
}
|
||||
if ( $times->{sched_dep} ) {
|
||||
$times->{sched_dep}
|
||||
= epoch_to_dt( $times->{sched_dep} );
|
||||
$times->{rt_dep} = $times->{sched_dep}->clone;
|
||||
if ( $times->{ddelay}
|
||||
and $times->{ddelay} =~ m{^\d+$} )
|
||||
{
|
||||
$times->{rt_dep}
|
||||
->add( minutes => $times->{ddelay} );
|
||||
}
|
||||
$times->{rt_dep_countdown}
|
||||
= $times->{rt_dep}->epoch - $epoch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ret->{departure_countdown}
|
||||
= $ret->{real_departure}->epoch - $now->epoch;
|
||||
if ( $in_transit->{real_arr_ts} ) {
|
||||
$ret->{arrival_countdown}
|
||||
= $ret->{real_arrival}->epoch - $now->epoch;
|
||||
$ret->{journey_duration} = $ret->{real_arrival}->epoch
|
||||
$ret->{journey_duration}
|
||||
= $ret->{real_arrival}->epoch
|
||||
- $ret->{real_departure}->epoch;
|
||||
$ret->{journey_completion}
|
||||
= $ret->{journey_duration}
|
||||
|
@ -2116,7 +2373,8 @@ sub startup {
|
|||
and $next_departure->epoch - $journey->{rt_arrival}->epoch
|
||||
< ( 60 * 60 ) )
|
||||
{
|
||||
if ( $next_departure->epoch - $journey->{rt_arrival}->epoch
|
||||
if (
|
||||
$next_departure->epoch - $journey->{rt_arrival}->epoch
|
||||
< 0 )
|
||||
{
|
||||
push( @inconsistencies,
|
||||
|
|
|
@ -60,6 +60,7 @@ sub run {
|
|||
},
|
||||
{ user_id => $uid }
|
||||
);
|
||||
$self->app->add_route_timestamps( $uid, $train );
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
|
@ -112,6 +113,7 @@ sub run {
|
|||
},
|
||||
{ user_id => $uid }
|
||||
);
|
||||
$self->app->add_route_timestamps( $uid, $train );
|
||||
}
|
||||
elsif ( $entry->{real_arr_ts} ) {
|
||||
$self->app->log->debug(" - checking out");
|
||||
|
|
|
@ -62,8 +62,56 @@
|
|||
noch nicht bekannt
|
||||
% }
|
||||
</div>
|
||||
<div class="center-align hide-on-small-only">
|
||||
% for my $station (@{$journey->{route_after}}) {
|
||||
% if ($station->[0] eq $journey->{arr_name}) {
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||
<%= $station->[0] %><br/>
|
||||
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
</div>
|
||||
<div style="clear: both;">
|
||||
</div>
|
||||
<div class="hide-on-med-and-up" style="margin-top: 2ex;">
|
||||
% for my $station (@{$journey->{route_after}}) {
|
||||
% if ($station->[0] eq $journey->{arr_name}) {
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||
Nächster Halt:<br/>
|
||||
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||
Aktueller Halt:<br/>
|
||||
<%= $station->[0] %><br/>
|
||||
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
</div>
|
||||
</p>
|
||||
% }
|
||||
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
|
||||
|
|
|
@ -70,8 +70,56 @@
|
|||
noch nicht bekannt
|
||||
% }
|
||||
</div>
|
||||
<div class="center-align hide-on-small-only">
|
||||
% for my $station (@{$journey->{route_after}}) {
|
||||
% if ($station->[0] eq $journey->{arr_name}) {
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||
<%= $station->[0] %><br/>
|
||||
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
</div>
|
||||
<div style="clear: both;">
|
||||
</div>
|
||||
<div class="hide-on-med-and-up" style="margin-top: 2ex;">
|
||||
% for my $station (@{$journey->{route_after}}) {
|
||||
% if ($station->[0] eq $journey->{arr_name}) {
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_arr_countdown} // 0) > 0) {
|
||||
Nächster Halt:<br/>
|
||||
<%= $station->[0] %><br/><%= $station->[1]{rt_arr}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_arr}->epoch != $station->[1]{rt_arr}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_arr}->epoch - $station->[1]{sched_arr}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% if (($station->[1]{rt_dep_countdown} // 0) > 0) {
|
||||
Aktueller Halt:<br/>
|
||||
<%= $station->[0] %><br/>
|
||||
<%= $station->[1]{rt_arr}->strftime('%H:%M') %> →
|
||||
<%= $station->[1]{rt_dep}->strftime('%H:%M') %>
|
||||
% if ($station->[1]{sched_dep}->epoch != $station->[1]{rt_dep}->epoch) {
|
||||
%= sprintf('(%+d)', ($station->[1]{rt_dep}->epoch - $station->[1]{sched_dep}->epoch ) / 60);
|
||||
% }
|
||||
% last;
|
||||
% }
|
||||
% }
|
||||
</div>
|
||||
</p>
|
||||
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
|
||||
<p style="margin-bottom: 2ex;">
|
||||
|
|
Loading…
Reference in a new issue