diff options
| author | Daniel Friesel <derf@finalrewind.org> | 2019-05-31 20:18:22 +0200 | 
|---|---|---|
| committer | Daniel Friesel <derf@finalrewind.org> | 2019-05-31 20:18:22 +0200 | 
| commit | b1591eed543b355cf30d77c6b9ec886fc5eb79b4 (patch) | |
| tree | 630e5435522114ac1cfbfb531a0bf8a4d848a3e0 | |
| parent | 155f9f39cc83fcf83e5dd48ae58b09736281c912 (diff) | |
show current/next stop while checked in
| -rwxr-xr-x | lib/Travelynx.pm | 266 | ||||
| -rw-r--r-- | lib/Travelynx/Command/work.pm | 2 | ||||
| -rw-r--r-- | templates/_checked_in.html.ep | 48 | ||||
| -rw-r--r-- | templates/_public_status_card.html.ep | 48 | 
4 files changed, 360 insertions, 4 deletions
| diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 19d6edf..3bc9e3c 100755 --- a/lib/Travelynx.pm +++ b/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 );  		}  	); @@ -1490,6 +1496,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) = @_; @@ -1826,8 +2049,9 @@ sub startup {  			$uid //= $self->current_user->{id}; -			my $db = $self->pg->db; -			my $now = DateTime->now( time_zone => 'Europe/Berlin' ); +			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, diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 46f2213..80c584c 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -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"); diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index 0683ae5..df5d260 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -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]) { diff --git a/templates/_public_status_card.html.ep b/templates/_public_status_card.html.ep index ebf989b..510bf52 100644 --- a/templates/_public_status_card.html.ep +++ b/templates/_public_status_card.html.ep @@ -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;"> | 
