Unverified Commit 087d3871 authored by Birte Kristina Friesel's avatar Birte Kristina Friesel
Browse files

Use Travel::Status::DE::HAFAS instead of traininfo.exe for journey details

parent bde63464
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ requires 'Mojolicious::Plugin::Authentication';
requires 'Mojo::Pg';
requires 'Text::CSV';
requires 'Travel::Status::DE::DBWagenreihung';
requires 'Travel::Status::DE::HAFAS';
requires 'Travel::Status::DE::IRIS';
requires 'UUID::Tiny';
requires 'JSON';
+29 −85
Original line number Diff line number Diff line
@@ -719,11 +719,15 @@ sub startup {
					if ( $station_data->{sched_arr} ) {
						my $sched_arr
						  = epoch_to_dt( $station_data->{sched_arr} );
						my $rt_arr = $sched_arr->clone;
						if (    $station_data->{adelay}
							and $station_data->{adelay} =~ m{^\d+$} )
						my $rt_arr = epoch_to_dt( $station_data->{rt_arr} );
						if ( $rt_arr->epoch == 0 ) {
							$rt_arr = $sched_arr->clone;
							if (    $station_data->{arr_delay}
								and $station_data->{arr_delay} =~ m{^\d+$} )
							{
							$rt_arr->add( minutes => $station_data->{adelay} );
								$rt_arr->add(
									minutes => $station_data->{arr_delay} );
							}
						}
						$self->in_transit->set_arrival_times(
							uid           => $uid,
@@ -1076,8 +1080,6 @@ sub startup {
			my $date_yyyy = $train->start->strftime('%d.%m.%Y');
			my $train_no  = $train->type . ' ' . $train->train_no;

			my ( $trainlink, $route_data );

			$self->hafas->get_json_p(
				"${base}&date=${date_yy}&trainname=${train_no}")->then(
				sub {
@@ -1085,7 +1087,6 @@ sub startup {

					# Fallback: Take first result
					my $result = $trainsearch->{suggestions}[0];
					$trainlink = $result->{trainLink};

					# Try finding a result for the current date
					for
@@ -1107,13 +1108,12 @@ sub startup {
            # instead.
							if ( $suggestion->{dep} eq $train->origin ) {
								$result = $suggestion;
								$trainlink = $suggestion->{trainLink};
								last;
							}
						}
					}

					if ( not $trainlink ) {
					if ( not $result ) {
						$self->app->log->debug("trainlink not found");
						return Mojo::Promise->reject("trainlink not found");
					}
@@ -1135,65 +1135,27 @@ sub startup {
						data => { trip_id => $trip_id }
					);

					my $base2
					  = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
					return $self->hafas->get_json_p(
"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_json.vs_hap"
					);
					return $self->hafas->get_route_timestamps_p(
						trip_id => $trip_id );
				}
			)->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 = {};
					my ( $route_data, $journey ) = @_;

					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,
							eva       => $station->{evaId},
						};
					}

					my $base2
					  = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn';
					return $self->hafas->get_xml_p(
						"${base2}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3"
					);
					for my $station ( @{$route} ) {
						$station->[1]
						  = $route_data->{ $station->[0] };
					}
			)->then(
				sub {
					my ($traininfo2) = @_;

					for my $station ( keys %{$route_data} ) {
						for my $key (
							keys %{ $traininfo2->{station}{$station} // {} } )
					my @messages;
					for my $m ( $journey->messages ) {
						push(
							@messages,
							{
							$route_data->{$station}{$key}
							  = $traininfo2->{station}{$station}{$key};
						}
								header => $m->short,
								lead   => $m->text,
							}

					for my $station ( @{$route} ) {
						$station->[1]
						  = $route_data->{ $station->[0] };
						);
					}

					$self->in_transit->set_route_data(
@@ -1208,7 +1170,7 @@ sub startup {
							map { [ $_->[0]->epoch, $_->[1] ] }
							  $train->qos_messages
						],
						him_messages => $traininfo2->{messages},
						him_messages => \@messages,
					);
					return;
				}
@@ -1585,13 +1547,7 @@ sub startup {
				if ( $dep_info and $dep_info->{sched_arr} ) {
					$dep_info->{sched_arr}
					  = epoch_to_dt( $dep_info->{sched_arr} );
					$dep_info->{rt_arr} = $dep_info->{sched_arr}->clone;
					if (    $dep_info->{adelay}
						and $dep_info->{adelay} =~ m{^\d+$} )
					{
						$dep_info->{rt_arr}
						  ->add( minutes => $dep_info->{adelay} );
					}
					$dep_info->{rt_arr} = epoch_to_dt( $dep_info->{rt_arr} );
					$dep_info->{rt_arr_countdown} = $ret->{boarding_countdown}
					  = $dep_info->{rt_arr}->epoch - $epoch;
				}
@@ -1610,13 +1566,7 @@ sub startup {
						{
							$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} = epoch_to_dt( $times->{rt_arr} );
							$times->{rt_arr_countdown}
							  = $times->{rt_arr}->epoch - $epoch;
						}
@@ -1625,13 +1575,7 @@ sub startup {
						{
							$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} = epoch_to_dt( $times->{rt_dep} );
							$times->{rt_dep_countdown}
							  = $times->{rt_dep}->epoch - $epoch;
						}
+58 −109
Original line number Diff line number Diff line
@@ -12,8 +12,15 @@ use DateTime;
use Encode qw(decode);
use JSON;
use Mojo::Promise;
use Travel::Status::DE::HAFAS;
use XML::LibXML;

sub _epoch {
	my ($dt) = @_;

	return $dt ? $dt->epoch : 0;
}

sub new {
	my ( $class, %opt ) = @_;

@@ -167,129 +174,71 @@ sub get_json_p {
	return $promise;
}

sub get_xml_p {
	my ( $self, $url ) = @_;
sub get_route_timestamps_p {
	my ( $self, %opt ) = @_;

	my $cache   = $self->{realtime_cache};
	my $promise = Mojo::Promise->new;

	if ( my $content = $cache->thaw($url) ) {
		return $promise->resolve($content);
	}

	$self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} )
	  ->then(
	my $now     = DateTime->now( time_zone => 'Europe/Berlin' );

	Travel::Status::DE::HAFAS->new_p(
		journey => {
			id => $opt{trip_id},

			# name => $opt{train_no},
		},
		cache      => $self->{realtime_cache},
		promise    => 'Mojo::Promise',
		user_agent => $self->{user_agent}->request_timeout(10)
	)->then(
		sub {
			my ($tx) = @_;

			if ( my $err = $tx->error ) {
				$promise->reject(
"hafas->get_xml_p($url) returned HTTP $err->{code} $err->{message}"
				);
				return;
			}

			my $body = decode( 'ISO-8859-15', $tx->res->body );
			my $tree;

			my $traininfo = {
				station  => {},
				messages => [],
			my ($hafas) = @_;
			my $journey = $hafas->result;
			my $ret     = {};

			my $station_is_past = 1;
			for my $stop ( $journey->route ) {
				my $name = $stop->{name};
				$ret->{$name} = {
					sched_arr   => _epoch( $stop->{sched_arr} ),
					sched_dep   => _epoch( $stop->{sched_dep} ),
					rt_arr      => _epoch( $stop->{rt_arr} ),
					rt_dep      => _epoch( $stop->{rt_dep} ),
					arr_delay   => $stop->{arr_delay},
					dep_delay   => $stop->{dep_delay},
					eva         => $stop->{eva},
					load        => $stop->{load},
					isCancelled => (
						( $stop->{arr_cancelled} or not $stop->{sched_arr} )
						  and
						  ( $stop->{dep_cancelled} or not $stop->{sched_dep} )
					),
				};

			# <SDay text="... &gt; ..."> is invalid XML, but present in
			# regardless. As it is the last tag, we just throw it away.
			$body =~ s{<SDay [^>]*/>}{}s;

			# More fixes for invalid XML
			$body =~ s{P&R}{P&amp;R};
			$body =~ s{& }{&amp; }g;

			# <Attribute [...] text="[...]"[...]"" /> is invalid XML.
			# Work around it.
			$body
			  =~ s{<Attribute([^>]+)text="([^"]*)"([^"=>]*)""}{<Attribute$1text="$2&#042;$3&#042;"}s;

			# Same for <HIMMessage lead="[...]"[...]"[...]" />
			$body
			  =~ s{<HIMMessage([^>]+)lead="([^"]*)"([^"=>]*)"([^"]*)"}{<Attribute$1text="$2&#042;$3&#042;$4"}s;

			# ... and <HIMMessage [...] lead="[...]<>[...]">
			# (replace <> with t$t)
			while ( $body
				=~ s{<HIMMessage([^>]+)lead="([^"]*)<>([^"=]*)"}{<HIMMessage$1lead="$2&#11020;$3"}gis
				if (
					    $station_is_past
					and not $ret->{$name}{isCancelled}
					and $now->epoch < (
						$ret->{$name}{rt_arr} // $ret->{$name}{rt_dep}
						  // $ret->{$name}{sched_arr}
						  // $ret->{$name}{sched_dep} // $now->epoch
					)
			{
			}

			# Dito for <HIMMessage [...] lead="[...]<br>[...]">.
			while ( $body
				=~ s{<HIMMessage([^>]+)lead="([^"]*)<br/?>([^"=]*)"}{<HIMMessage$1lead="$2 $3"}is
				  )
				{
					$station_is_past = 0;
				}

			# ... and any other HTML tag inside an XML attribute
			while ( $body
				=~ s{<HIMMessage([^>]+)lead="([^"]*)<[^>]+>([^"=]*)"}{<HIMMessage$1lead="$2$3"}is
			  )
			{
				$ret->{$name}{isPast} = $station_is_past;
			}

			eval { $tree = XML::LibXML->load_xml( string => $body ) };
			if ( my $err = $@ ) {
				if ( $err =~ m{extra content at the end}i ) {

					# We requested XML, but received an HTML error page
					# (which was returned with HTTP 200 OK).
					$self->{log}->debug("load_xml($url): $err");
				}
				else {
					# There is invalid XML which we might be able to fix via
					# regular expressions, so dump it into the production log.
					$self->{log}->info("load_xml($url): $err");
				}
				$cache->freeze( $url, $traininfo );
				$promise->reject("hafas->get_xml_p($url): $err");
				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,
				};
			}

			for my $message ( $tree->findnodes('/Journey/HIMMessage') ) {
				my $header  = $message->getAttribute('header');
				my $lead    = $message->getAttribute('lead');
				my $display = $message->getAttribute('display');
				push(
					@{ $traininfo->{messages} },
					{
						header  => $header,
						lead    => $lead,
						display => $display
					}
				);
			}

			$cache->freeze( $url, $traininfo );
			$promise->resolve($traininfo);
			$promise->resolve( $ret, $journey );
			return;
		}
	)->catch(
		sub {
			my ($err) = @_;
			$self->{log}->info("hafas->get_xml_p($url): $err");
			$promise->reject("hafas->get_xml_p($url): $err");
			$promise->reject($err);
			return;
		}
	)->wait;

	return $promise;
}

+4 −2
Original line number Diff line number Diff line
@@ -3,9 +3,11 @@
		<a href="https://finalrewind.org/projects/travelynx">travelynx</a> v<%= stash('version') // '???' %><br/>
		Entwickelt von <a href="https://twitter.com/derfnull">@derfnull</a><br/>
		<a href="<%= app->config->{ref}{source} // 'https://github.com/derf/travelynx' %>">Quelltext</a> lizensiert unter AGPL v3<br/><br/>
		Backend:
		Backends:
		<a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
		v<%= $Travel::Status::DE::IRIS::VERSION %><br/>
		v<%= $Travel::Status::DE::IRIS::VERSION %> und
		<a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
		v<%= $Travel::Status::DE::HAFAS::VERSION %><br/>
		<a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellendaten</a>
		© DB Station&amp;Service AG,
		Europaplatz 1,