show current/next stop while checked in

This commit is contained in:
Daniel Friesel 2019-05-31 20:18:22 +02:00
parent 155f9f39cc
commit b1591eed54
4 changed files with 360 additions and 4 deletions

View file

@ -2,10 +2,12 @@ package Travelynx;
use Mojo::Base 'Mojolicious'; use Mojo::Base 'Mojolicious';
use Mojo::Pg; use Mojo::Pg;
use Mojo::Promise;
use Mojolicious::Plugin::Authentication; use Mojolicious::Plugin::Authentication;
use Cache::File; use Cache::File;
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
use DateTime; use DateTime;
use DateTime::Format::Strptime;
use Encode qw(decode encode); use Encode qw(decode encode);
use Geo::Distance; use Geo::Distance;
use JSON; use JSON;
@ -14,6 +16,7 @@ use List::MoreUtils qw(after_incl before_incl);
use Travel::Status::DE::IRIS; use Travel::Status::DE::IRIS;
use Travel::Status::DE::IRIS::Stations; use Travel::Status::DE::IRIS::Stations;
use Travelynx::Helper::Sendmail; use Travelynx::Helper::Sendmail;
use XML::LibXML;
sub check_password { sub check_password {
my ( $password, $hash ) = @_; my ( $password, $hash ) = @_;
@ -395,6 +398,8 @@ sub startup {
"Checkin($uid): INSERT failed: $@"); "Checkin($uid): INSERT failed: $@");
return ( undef, 'INSERT failed: ' . $@ ); return ( undef, 'INSERT failed: ' . $@ );
} }
$self->add_route_timestamps( $self->current_user->{id},
$train );
$self->run_hook( $self->current_user->{id}, 'checkin' ); $self->run_hook( $self->current_user->{id}, 'checkin' );
return ( $train, undef ); return ( $train, undef );
} }
@ -630,6 +635,7 @@ sub startup {
return ( 0, undef ); return ( 0, undef );
} }
$self->run_hook( $uid, 'update' ); $self->run_hook( $uid, 'update' );
$self->add_route_timestamps( $self->current_user->{id}, $train );
return ( 1, undef ); 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="... &gt; ..."> 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( $self->helper(
'get_oldest_journey_ts' => sub { 'get_oldest_journey_ts' => sub {
my ($self) = @_; my ($self) = @_;
@ -1826,8 +2049,9 @@ sub startup {
$uid //= $self->current_user->{id}; $uid //= $self->current_user->{id};
my $db = $self->pg->db; my $db = $self->pg->db;
my $now = DateTime->now( time_zone => 'Europe/Berlin' ); my $now = DateTime->now( time_zone => 'Europe/Berlin' );
my $epoch = $now->epoch;
my $in_transit my $in_transit
= $db->select( 'in_transit_str', '*', { user_id => $uid } ) = $db->select( 'in_transit_str', '*', { user_id => $uid } )
@ -1883,12 +2107,45 @@ sub startup {
} }
$ret->{messages} = [ reverse @parsed_messages ]; $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->{departure_countdown}
= $ret->{real_departure}->epoch - $now->epoch; = $ret->{real_departure}->epoch - $now->epoch;
if ( $in_transit->{real_arr_ts} ) { if ( $in_transit->{real_arr_ts} ) {
$ret->{arrival_countdown} $ret->{arrival_countdown}
= $ret->{real_arrival}->epoch - $now->epoch; = $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->{real_departure}->epoch;
$ret->{journey_completion} $ret->{journey_completion}
= $ret->{journey_duration} = $ret->{journey_duration}
@ -2116,7 +2373,8 @@ sub startup {
and $next_departure->epoch - $journey->{rt_arrival}->epoch and $next_departure->epoch - $journey->{rt_arrival}->epoch
< ( 60 * 60 ) ) < ( 60 * 60 ) )
{ {
if ( $next_departure->epoch - $journey->{rt_arrival}->epoch if (
$next_departure->epoch - $journey->{rt_arrival}->epoch
< 0 ) < 0 )
{ {
push( @inconsistencies, push( @inconsistencies,

View file

@ -60,6 +60,7 @@ sub run {
}, },
{ user_id => $uid } { user_id => $uid }
); );
$self->app->add_route_timestamps( $uid, $train );
} }
}; };
if ($@) { if ($@) {
@ -112,6 +113,7 @@ sub run {
}, },
{ user_id => $uid } { user_id => $uid }
); );
$self->app->add_route_timestamps( $uid, $train );
} }
elsif ( $entry->{real_arr_ts} ) { elsif ( $entry->{real_arr_ts} ) {
$self->app->log->debug(" - checking out"); $self->app->log->debug(" - checking out");

View file

@ -62,8 +62,56 @@
noch nicht bekannt noch nicht bekannt
% } % }
</div> </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 style="clear: both;">
</div> </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> </p>
% } % }
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) { % if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {

View file

@ -70,8 +70,56 @@
noch nicht bekannt noch nicht bekannt
% } % }
</div> </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 style="clear: both;">
</div> </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> </p>
% if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) { % if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) {
<p style="margin-bottom: 2ex;"> <p style="margin-bottom: 2ex;">