Loading lib/DBInfoscreen.pm +14 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ use Mojo::Base 'Mojolicious'; # License: 2-Clause BSD use Cache::File; use DBInfoscreen::Helper::HAFAS; use File::Slurp qw(read_file); use JSON; use Travel::Status::DE::HAFAS; Loading Loading @@ -94,6 +95,19 @@ sub startup { } ); $self->helper( hafas => sub { my ($self) = @_; state $hafas = DBInfoscreen::Helper::HAFAS->new( log => $self->app->log, main_cache => $self->app->cache_iris_main, realtime_cache => $self->app->cache_iris_rt, user_agent => $self->ua, version => $VERSION, ); } ); $self->helper( 'handle_no_results' => sub { my ( $self, $backend, $station, $errstr ) = @_; Loading lib/DBInfoscreen/Controller/Stationboard.pm +3 −280 Original line number Diff line number Diff line Loading @@ -120,44 +120,6 @@ sub check_wagonorder_with_wings { return; } sub get_hafas_trip_id { my ( $ua, $cache, $train ) = @_; my $eva = $train->station_uic; my $dep_ts = DateTime->now( time_zone => 'Europe/Berlin' ); my $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; if ( $train->sched_departure ) { $dep_ts = $train->sched_departure->epoch; $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; } elsif ( $train->sched_arrival ) { $dep_ts = $train->sched_arrival->epoch; $url = "https://2.db.transport.rest/stations/${eva}/arrivals?duration=5&when=$dep_ts"; } my $json = hafas_rest_req( $ua, $cache, $url ); #say "looking for " . $train->train_no . " in $url"; for my $result ( @{ $json // [] } ) { my $trip_id = $result->{tripId}; my $fahrt = $result->{line}{fahrtNr}; #say "checking $fahrt"; if ( $result->{line} and $result->{line}{fahrtNr} == $train->train_no ) { #say "Trip ID is $trip_id"; return $trip_id; } else { #say "unmatched Trip ID $trip_id"; } } return; } sub check_wagonorder { my ( $ua, $cache, $train_no, $wr_link ) = @_; Loading @@ -184,240 +146,6 @@ sub check_wagonorder { } } sub hafas_rest_req { my ( $ua, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $ua->get( $url => { 'User-Agent' => "dbf.finalrewind.org/${dbf_version}" } ) ->result; }; if ($@) { return; } if ( $res->is_error ) { return; } my $json = decode_json( $res->body ); $cache->freeze( $url, $json ); return $json; } sub hafas_json_req { my ( $ua, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $ua->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { return; } my $body = encode( 'utf-8', decode( 'ISO-8859-15', $res->body ) ); $body =~ s{^TSLs[.]sls = }{}; $body =~ s{;$}{}; $body =~ s{(}{(}g; $body =~ s{)}{)}g; my $json = decode_json($body); $cache->freeze( $url, $json ); return $json; } sub hafas_xml_req { my ( $ua, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $ua->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { $cache->freeze( $url, {} ); return; } my $body = decode( 'ISO-8859-15', $res->body ); # <SDay text="... > ..."> is invalid HTML, but present # regardless. As it is the last tag, we just throw it away. $body =~ s{<SDay [^>]*/>}{}s; my $tree; eval { $tree = XML::LibXML->load_xml( string => $body ) }; if ($@) { $cache->freeze( $url, {} ); return; } my $ret = { station => {}, stations => [], messages => [], }; for my $station ( $tree->findnodes('/Journey/St') ) { my $name = $station->getAttribute('name'); my $adelay = $station->getAttribute('adelay'); my $ddelay = $station->getAttribute('ddelay'); push( @{ $ret->{stations} }, $name ); $ret->{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( @{ $ret->{messages} }, { header => $header, lead => $lead, display => $display } ); } $cache->freeze( $url, $ret ); return $ret; } # quick&dirty, will be cleaned up later sub get_route_timestamps { my ( $ua, $cache_main, $cache_rt, $opt ) = @_; $ua->request_timeout(3); my $base = 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json&start=yes&rt=1'; my ( $date_yy, $date_yyyy, $train_no, $train_origin ); if ( $opt->{train} ) { $date_yy = $opt->{train}->start->strftime('%d.%m.%y'); $date_yyyy = $opt->{train}->start->strftime('%d.%m.%Y'); $train_no = $opt->{train}->type . ' ' . $opt->{train}->train_no; $train_origin = $opt->{train}->origin; } else { my $now = DateTime->now( time_zone => 'Europe/Berlin' ); $date_yy = $now->strftime('%d.%m.%y'); $date_yyyy = $now->strftime('%d.%m.%Y'); $train_no = $opt->{train_no}; } my $trainsearch = hafas_json_req( $ua, $cache_main, "${base}&date=${date_yy}&trainname=${train_no}" ); if ( not $trainsearch ) { return; } # Fallback: Take first result my $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 ( exists $suggestion->{depDate} and ( $suggestion->{depDate} eq $date_yy or $suggestion->{depDate} eq $date_yyyy ) ) { # Train numbers are not unique, e.g. IC 149 refers both to the # InterCity service Amsterdam -> Berlin and to the InterCity service # Koebenhavns Lufthavn st -> Aarhus. One workaround is making # requests with the stationFilter=80 parameter. Checking the origin # station seems to be the more generic solution, so we do that # instead. if ( $train_origin and $suggestion->{dep} eq $train_origin ) { $trainlink = $suggestion->{trainLink}; last; } } } if ( not $trainlink ) { return; } $base = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn'; my $traininfo = hafas_json_req( $ua, $cache_rt, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_json" ); if ( not $traininfo or $traininfo->{error} ) { return; } my $traindelay = hafas_xml_req( $ua, $cache_rt, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3" ); my $ret = {}; my $strp = DateTime::Format::Strptime->new( pattern => '%d.%m.%y %H:%M', time_zone => 'Europe/Berlin', ); for my $station ( @{ $traininfo->{suggestions}[0]{locations} // [] } ) { my $name = $station->{name}; my $arr = $station->{arrDate} . ' ' . $station->{arrTime}; my $dep = $station->{depDate} . ' ' . $station->{depTime}; $ret->{$name} = { sched_arr => scalar $strp->parse_datetime($arr), sched_dep => scalar $strp->parse_datetime($dep), }; if ( exists $traindelay->{station}{$name} ) { my $delay = $traindelay->{station}{$name}; if ( $ret->{$name}{sched_arr} and $delay->{adelay} and $delay->{adelay} =~ m{^\d+$} ) { $ret->{$name}{rt_arr} = $ret->{$name}{sched_arr} ->clone->add( minutes => $delay->{adelay} ); } if ( $ret->{$name}{sched_dep} and $delay->{ddelay} and $delay->{ddelay} =~ m{^\d+$} ) { $ret->{$name}{rt_dep} = $ret->{$name}{sched_dep} ->clone->add( minutes => $delay->{ddelay} ); } } } return ( $ret, $traindelay // {} ); } sub get_results_for { my ( $backend, $station, %opt ) = @_; my $data; Loading Loading @@ -725,8 +453,7 @@ sub render_train { ) ]; $departure->{trip_id} = get_hafas_trip_id( $self->ua, $self->app->cache_iris_main, $result ); $departure->{trip_id} = $self->hafas->get_tripid($result); if ( $departure->{wr_link} Loading @@ -739,12 +466,8 @@ sub render_train { $departure->{wr_link} = undef; } my ( $route_ts, $route_info ) = get_route_timestamps( $self->ua, $self->app->cache_iris_main, $self->app->cache_iris_rt, { train => $result } ); my ( $route_ts, $route_info ) = $self->hafas->get_route_timestamps( train => $result ); # If a train number changes on the way, IRIS routes are incomplete, # whereas HAFAS data has all stops -> merge HAFAS stops into IRIS Loading lib/DBInfoscreen/Helper/HAFAS.pm 0 → 100644 +293 −0 Original line number Diff line number Diff line package DBInfoscreen::Helper::HAFAS; use strict; use warnings; use 5.020; use DateTime; use Encode qw(decode encode); use Mojo::JSON qw(decode_json); use XML::LibXML; sub new { my ( $class, %opt ) = @_; my $version = $opt{version}; $opt{header} = { 'User-Agent' => "dbf/${version} +https://finalrewind.org/projects/db-fakedisplay" }; return bless( \%opt, $class ); } sub hafas_rest_req { my ( $self, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $self->{user_agent}->get($url)->result; }; if ($@) { return; } if ( $res->is_error ) { return; } my $json = decode_json( $res->body ); $cache->freeze( $url, $json ); return $json; } sub hafas_json_req { my ( $self, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $self->{user_agent}->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { return; } my $body = encode( 'utf-8', decode( 'ISO-8859-15', $res->body ) ); $body =~ s{^TSLs[.]sls = }{}; $body =~ s{;$}{}; $body =~ s{(}{(}g; $body =~ s{)}{)}g; my $json = decode_json($body); $cache->freeze( $url, $json ); return $json; } sub hafas_xml_req { my ( $self, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $self->{user_agent}->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { $cache->freeze( $url, {} ); return; } my $body = decode( 'ISO-8859-15', $res->body ); # <SDay text="... > ..."> is invalid HTML, but present # regardless. As it is the last tag, we just throw it away. $body =~ s{<SDay [^>]*/>}{}s; my $tree; eval { $tree = XML::LibXML->load_xml( string => $body ) }; if ($@) { $cache->freeze( $url, {} ); return; } my $ret = { station => {}, stations => [], messages => [], }; for my $station ( $tree->findnodes('/Journey/St') ) { my $name = $station->getAttribute('name'); my $adelay = $station->getAttribute('adelay'); my $ddelay = $station->getAttribute('ddelay'); push( @{ $ret->{stations} }, $name ); $ret->{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( @{ $ret->{messages} }, { header => $header, lead => $lead, display => $display } ); } $cache->freeze( $url, $ret ); return $ret; } sub get_route_timestamps { my ( $self, %opt ) = @_; my $base = 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json&start=yes&rt=1'; my ( $date_yy, $date_yyyy, $train_no, $train_origin ); if ( $opt{train} ) { $date_yy = $opt{train}->start->strftime('%d.%m.%y'); $date_yyyy = $opt{train}->start->strftime('%d.%m.%Y'); $train_no = $opt{train}->type . ' ' . $opt{train}->train_no; $train_origin = $opt{train}->origin; } else { my $now = DateTime->now( time_zone => 'Europe/Berlin' ); $date_yy = $now->strftime('%d.%m.%y'); $date_yyyy = $now->strftime('%d.%m.%Y'); $train_no = $opt{train_no}; } my $trainsearch = $self->hafas_json_req( $self->{main_cache}, "${base}&date=${date_yy}&trainname=${train_no}" ); if ( not $trainsearch ) { return; } # Fallback: Take first result my $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 ( exists $suggestion->{depDate} and ( $suggestion->{depDate} eq $date_yy or $suggestion->{depDate} eq $date_yyyy ) ) { # Train numbers are not unique, e.g. IC 149 refers both to the # InterCity service Amsterdam -> Berlin and to the InterCity service # Koebenhavns Lufthavn st -> Aarhus. One workaround is making # requests with the stationFilter=80 parameter. Checking the origin # station seems to be the more generic solution, so we do that # instead. if ( $train_origin and $suggestion->{dep} eq $train_origin ) { $trainlink = $suggestion->{trainLink}; last; } } } if ( not $trainlink ) { return; } $base = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn'; my $traininfo = $self->hafas_json_req( $self->{realtime_cache}, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_json" ); if ( not $traininfo or $traininfo->{error} ) { return; } my $traindelay = $self->hafas_xml_req( $self->{realtime_cache}, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3" ); my $ret = {}; my $strp = DateTime::Format::Strptime->new( pattern => '%d.%m.%y %H:%M', time_zone => 'Europe/Berlin', ); for my $station ( @{ $traininfo->{suggestions}[0]{locations} // [] } ) { my $name = $station->{name}; my $arr = $station->{arrDate} . ' ' . $station->{arrTime}; my $dep = $station->{depDate} . ' ' . $station->{depTime}; $ret->{$name} = { sched_arr => scalar $strp->parse_datetime($arr), sched_dep => scalar $strp->parse_datetime($dep), }; if ( exists $traindelay->{station}{$name} ) { my $delay = $traindelay->{station}{$name}; if ( $ret->{$name}{sched_arr} and $delay->{adelay} and $delay->{adelay} =~ m{^\d+$} ) { $ret->{$name}{rt_arr} = $ret->{$name}{sched_arr} ->clone->add( minutes => $delay->{adelay} ); } if ( $ret->{$name}{sched_dep} and $delay->{ddelay} and $delay->{ddelay} =~ m{^\d+$} ) { $ret->{$name}{rt_dep} = $ret->{$name}{sched_dep} ->clone->add( minutes => $delay->{ddelay} ); } } } return ( $ret, $traindelay // {} ); } sub get_tripid { my ( $self, $train ) = @_; my $cache = $self->{main_cache}; my $eva = $train->station_uic; my $dep_ts = DateTime->now( time_zone => 'Europe/Berlin' ); my $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; if ( $train->sched_departure ) { $dep_ts = $train->sched_departure->epoch; $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; } elsif ( $train->sched_arrival ) { $dep_ts = $train->sched_arrival->epoch; $url = "https://2.db.transport.rest/stations/${eva}/arrivals?duration=5&when=$dep_ts"; } my $json = $self->hafas_rest_req( $cache, $url ); #say "looking for " . $train->train_no . " in $url"; for my $result ( @{ $json // [] } ) { my $trip_id = $result->{tripId}; my $fahrt = $result->{line}{fahrtNr}; #say "checking $fahrt"; if ( $result->{line} and $result->{line}{fahrtNr} == $train->train_no ) { #say "Trip ID is $trip_id"; return $trip_id; } else { #say "unmatched Trip ID $trip_id"; } } return; } 1; Loading
lib/DBInfoscreen.pm +14 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ use Mojo::Base 'Mojolicious'; # License: 2-Clause BSD use Cache::File; use DBInfoscreen::Helper::HAFAS; use File::Slurp qw(read_file); use JSON; use Travel::Status::DE::HAFAS; Loading Loading @@ -94,6 +95,19 @@ sub startup { } ); $self->helper( hafas => sub { my ($self) = @_; state $hafas = DBInfoscreen::Helper::HAFAS->new( log => $self->app->log, main_cache => $self->app->cache_iris_main, realtime_cache => $self->app->cache_iris_rt, user_agent => $self->ua, version => $VERSION, ); } ); $self->helper( 'handle_no_results' => sub { my ( $self, $backend, $station, $errstr ) = @_; Loading
lib/DBInfoscreen/Controller/Stationboard.pm +3 −280 Original line number Diff line number Diff line Loading @@ -120,44 +120,6 @@ sub check_wagonorder_with_wings { return; } sub get_hafas_trip_id { my ( $ua, $cache, $train ) = @_; my $eva = $train->station_uic; my $dep_ts = DateTime->now( time_zone => 'Europe/Berlin' ); my $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; if ( $train->sched_departure ) { $dep_ts = $train->sched_departure->epoch; $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; } elsif ( $train->sched_arrival ) { $dep_ts = $train->sched_arrival->epoch; $url = "https://2.db.transport.rest/stations/${eva}/arrivals?duration=5&when=$dep_ts"; } my $json = hafas_rest_req( $ua, $cache, $url ); #say "looking for " . $train->train_no . " in $url"; for my $result ( @{ $json // [] } ) { my $trip_id = $result->{tripId}; my $fahrt = $result->{line}{fahrtNr}; #say "checking $fahrt"; if ( $result->{line} and $result->{line}{fahrtNr} == $train->train_no ) { #say "Trip ID is $trip_id"; return $trip_id; } else { #say "unmatched Trip ID $trip_id"; } } return; } sub check_wagonorder { my ( $ua, $cache, $train_no, $wr_link ) = @_; Loading @@ -184,240 +146,6 @@ sub check_wagonorder { } } sub hafas_rest_req { my ( $ua, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $ua->get( $url => { 'User-Agent' => "dbf.finalrewind.org/${dbf_version}" } ) ->result; }; if ($@) { return; } if ( $res->is_error ) { return; } my $json = decode_json( $res->body ); $cache->freeze( $url, $json ); return $json; } sub hafas_json_req { my ( $ua, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $ua->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { return; } my $body = encode( 'utf-8', decode( 'ISO-8859-15', $res->body ) ); $body =~ s{^TSLs[.]sls = }{}; $body =~ s{;$}{}; $body =~ s{(}{(}g; $body =~ s{)}{)}g; my $json = decode_json($body); $cache->freeze( $url, $json ); return $json; } sub hafas_xml_req { my ( $ua, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $ua->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { $cache->freeze( $url, {} ); return; } my $body = decode( 'ISO-8859-15', $res->body ); # <SDay text="... > ..."> is invalid HTML, but present # regardless. As it is the last tag, we just throw it away. $body =~ s{<SDay [^>]*/>}{}s; my $tree; eval { $tree = XML::LibXML->load_xml( string => $body ) }; if ($@) { $cache->freeze( $url, {} ); return; } my $ret = { station => {}, stations => [], messages => [], }; for my $station ( $tree->findnodes('/Journey/St') ) { my $name = $station->getAttribute('name'); my $adelay = $station->getAttribute('adelay'); my $ddelay = $station->getAttribute('ddelay'); push( @{ $ret->{stations} }, $name ); $ret->{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( @{ $ret->{messages} }, { header => $header, lead => $lead, display => $display } ); } $cache->freeze( $url, $ret ); return $ret; } # quick&dirty, will be cleaned up later sub get_route_timestamps { my ( $ua, $cache_main, $cache_rt, $opt ) = @_; $ua->request_timeout(3); my $base = 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json&start=yes&rt=1'; my ( $date_yy, $date_yyyy, $train_no, $train_origin ); if ( $opt->{train} ) { $date_yy = $opt->{train}->start->strftime('%d.%m.%y'); $date_yyyy = $opt->{train}->start->strftime('%d.%m.%Y'); $train_no = $opt->{train}->type . ' ' . $opt->{train}->train_no; $train_origin = $opt->{train}->origin; } else { my $now = DateTime->now( time_zone => 'Europe/Berlin' ); $date_yy = $now->strftime('%d.%m.%y'); $date_yyyy = $now->strftime('%d.%m.%Y'); $train_no = $opt->{train_no}; } my $trainsearch = hafas_json_req( $ua, $cache_main, "${base}&date=${date_yy}&trainname=${train_no}" ); if ( not $trainsearch ) { return; } # Fallback: Take first result my $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 ( exists $suggestion->{depDate} and ( $suggestion->{depDate} eq $date_yy or $suggestion->{depDate} eq $date_yyyy ) ) { # Train numbers are not unique, e.g. IC 149 refers both to the # InterCity service Amsterdam -> Berlin and to the InterCity service # Koebenhavns Lufthavn st -> Aarhus. One workaround is making # requests with the stationFilter=80 parameter. Checking the origin # station seems to be the more generic solution, so we do that # instead. if ( $train_origin and $suggestion->{dep} eq $train_origin ) { $trainlink = $suggestion->{trainLink}; last; } } } if ( not $trainlink ) { return; } $base = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn'; my $traininfo = hafas_json_req( $ua, $cache_rt, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_json" ); if ( not $traininfo or $traininfo->{error} ) { return; } my $traindelay = hafas_xml_req( $ua, $cache_rt, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3" ); my $ret = {}; my $strp = DateTime::Format::Strptime->new( pattern => '%d.%m.%y %H:%M', time_zone => 'Europe/Berlin', ); for my $station ( @{ $traininfo->{suggestions}[0]{locations} // [] } ) { my $name = $station->{name}; my $arr = $station->{arrDate} . ' ' . $station->{arrTime}; my $dep = $station->{depDate} . ' ' . $station->{depTime}; $ret->{$name} = { sched_arr => scalar $strp->parse_datetime($arr), sched_dep => scalar $strp->parse_datetime($dep), }; if ( exists $traindelay->{station}{$name} ) { my $delay = $traindelay->{station}{$name}; if ( $ret->{$name}{sched_arr} and $delay->{adelay} and $delay->{adelay} =~ m{^\d+$} ) { $ret->{$name}{rt_arr} = $ret->{$name}{sched_arr} ->clone->add( minutes => $delay->{adelay} ); } if ( $ret->{$name}{sched_dep} and $delay->{ddelay} and $delay->{ddelay} =~ m{^\d+$} ) { $ret->{$name}{rt_dep} = $ret->{$name}{sched_dep} ->clone->add( minutes => $delay->{ddelay} ); } } } return ( $ret, $traindelay // {} ); } sub get_results_for { my ( $backend, $station, %opt ) = @_; my $data; Loading Loading @@ -725,8 +453,7 @@ sub render_train { ) ]; $departure->{trip_id} = get_hafas_trip_id( $self->ua, $self->app->cache_iris_main, $result ); $departure->{trip_id} = $self->hafas->get_tripid($result); if ( $departure->{wr_link} Loading @@ -739,12 +466,8 @@ sub render_train { $departure->{wr_link} = undef; } my ( $route_ts, $route_info ) = get_route_timestamps( $self->ua, $self->app->cache_iris_main, $self->app->cache_iris_rt, { train => $result } ); my ( $route_ts, $route_info ) = $self->hafas->get_route_timestamps( train => $result ); # If a train number changes on the way, IRIS routes are incomplete, # whereas HAFAS data has all stops -> merge HAFAS stops into IRIS Loading
lib/DBInfoscreen/Helper/HAFAS.pm 0 → 100644 +293 −0 Original line number Diff line number Diff line package DBInfoscreen::Helper::HAFAS; use strict; use warnings; use 5.020; use DateTime; use Encode qw(decode encode); use Mojo::JSON qw(decode_json); use XML::LibXML; sub new { my ( $class, %opt ) = @_; my $version = $opt{version}; $opt{header} = { 'User-Agent' => "dbf/${version} +https://finalrewind.org/projects/db-fakedisplay" }; return bless( \%opt, $class ); } sub hafas_rest_req { my ( $self, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $self->{user_agent}->get($url)->result; }; if ($@) { return; } if ( $res->is_error ) { return; } my $json = decode_json( $res->body ); $cache->freeze( $url, $json ); return $json; } sub hafas_json_req { my ( $self, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $self->{user_agent}->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { return; } my $body = encode( 'utf-8', decode( 'ISO-8859-15', $res->body ) ); $body =~ s{^TSLs[.]sls = }{}; $body =~ s{;$}{}; $body =~ s{(}{(}g; $body =~ s{)}{)}g; my $json = decode_json($body); $cache->freeze( $url, $json ); return $json; } sub hafas_xml_req { my ( $self, $cache, $url ) = @_; if ( my $content = $cache->thaw($url) ) { return $content; } my $res = eval { $self->{user_agent}->get($url)->result }; if ($@) { return; } if ( $res->is_error ) { $cache->freeze( $url, {} ); return; } my $body = decode( 'ISO-8859-15', $res->body ); # <SDay text="... > ..."> is invalid HTML, but present # regardless. As it is the last tag, we just throw it away. $body =~ s{<SDay [^>]*/>}{}s; my $tree; eval { $tree = XML::LibXML->load_xml( string => $body ) }; if ($@) { $cache->freeze( $url, {} ); return; } my $ret = { station => {}, stations => [], messages => [], }; for my $station ( $tree->findnodes('/Journey/St') ) { my $name = $station->getAttribute('name'); my $adelay = $station->getAttribute('adelay'); my $ddelay = $station->getAttribute('ddelay'); push( @{ $ret->{stations} }, $name ); $ret->{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( @{ $ret->{messages} }, { header => $header, lead => $lead, display => $display } ); } $cache->freeze( $url, $ret ); return $ret; } sub get_route_timestamps { my ( $self, %opt ) = @_; my $base = 'https://reiseauskunft.bahn.de/bin/trainsearch.exe/dn?L=vs_json&start=yes&rt=1'; my ( $date_yy, $date_yyyy, $train_no, $train_origin ); if ( $opt{train} ) { $date_yy = $opt{train}->start->strftime('%d.%m.%y'); $date_yyyy = $opt{train}->start->strftime('%d.%m.%Y'); $train_no = $opt{train}->type . ' ' . $opt{train}->train_no; $train_origin = $opt{train}->origin; } else { my $now = DateTime->now( time_zone => 'Europe/Berlin' ); $date_yy = $now->strftime('%d.%m.%y'); $date_yyyy = $now->strftime('%d.%m.%Y'); $train_no = $opt{train_no}; } my $trainsearch = $self->hafas_json_req( $self->{main_cache}, "${base}&date=${date_yy}&trainname=${train_no}" ); if ( not $trainsearch ) { return; } # Fallback: Take first result my $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 ( exists $suggestion->{depDate} and ( $suggestion->{depDate} eq $date_yy or $suggestion->{depDate} eq $date_yyyy ) ) { # Train numbers are not unique, e.g. IC 149 refers both to the # InterCity service Amsterdam -> Berlin and to the InterCity service # Koebenhavns Lufthavn st -> Aarhus. One workaround is making # requests with the stationFilter=80 parameter. Checking the origin # station seems to be the more generic solution, so we do that # instead. if ( $train_origin and $suggestion->{dep} eq $train_origin ) { $trainlink = $suggestion->{trainLink}; last; } } } if ( not $trainlink ) { return; } $base = 'https://reiseauskunft.bahn.de/bin/traininfo.exe/dn'; my $traininfo = $self->hafas_json_req( $self->{realtime_cache}, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_json" ); if ( not $traininfo or $traininfo->{error} ) { return; } my $traindelay = $self->hafas_xml_req( $self->{realtime_cache}, "${base}/${trainlink}?rt=1&date=${date_yy}&L=vs_java3" ); my $ret = {}; my $strp = DateTime::Format::Strptime->new( pattern => '%d.%m.%y %H:%M', time_zone => 'Europe/Berlin', ); for my $station ( @{ $traininfo->{suggestions}[0]{locations} // [] } ) { my $name = $station->{name}; my $arr = $station->{arrDate} . ' ' . $station->{arrTime}; my $dep = $station->{depDate} . ' ' . $station->{depTime}; $ret->{$name} = { sched_arr => scalar $strp->parse_datetime($arr), sched_dep => scalar $strp->parse_datetime($dep), }; if ( exists $traindelay->{station}{$name} ) { my $delay = $traindelay->{station}{$name}; if ( $ret->{$name}{sched_arr} and $delay->{adelay} and $delay->{adelay} =~ m{^\d+$} ) { $ret->{$name}{rt_arr} = $ret->{$name}{sched_arr} ->clone->add( minutes => $delay->{adelay} ); } if ( $ret->{$name}{sched_dep} and $delay->{ddelay} and $delay->{ddelay} =~ m{^\d+$} ) { $ret->{$name}{rt_dep} = $ret->{$name}{sched_dep} ->clone->add( minutes => $delay->{ddelay} ); } } } return ( $ret, $traindelay // {} ); } sub get_tripid { my ( $self, $train ) = @_; my $cache = $self->{main_cache}; my $eva = $train->station_uic; my $dep_ts = DateTime->now( time_zone => 'Europe/Berlin' ); my $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; if ( $train->sched_departure ) { $dep_ts = $train->sched_departure->epoch; $url = "https://2.db.transport.rest/stations/${eva}/departures?duration=5&when=$dep_ts"; } elsif ( $train->sched_arrival ) { $dep_ts = $train->sched_arrival->epoch; $url = "https://2.db.transport.rest/stations/${eva}/arrivals?duration=5&when=$dep_ts"; } my $json = $self->hafas_rest_req( $cache, $url ); #say "looking for " . $train->train_no . " in $url"; for my $result ( @{ $json // [] } ) { my $trip_id = $result->{tripId}; my $fahrt = $result->{line}{fahrtNr}; #say "checking $fahrt"; if ( $result->{line} and $result->{line}{fahrtNr} == $train->train_no ) { #say "Trip ID is $trip_id"; return $trip_id; } else { #say "unmatched Trip ID $trip_id"; } } return; } 1;