diff options
author | Birte Kristina Friesel <derf@finalrewind.org> | 2025-01-18 22:18:48 +0100 |
---|---|---|
committer | Birte Kristina Friesel <derf@finalrewind.org> | 2025-01-18 22:18:48 +0100 |
commit | 9bac2c56e91db08d9081727549a8bbf84f3a7ee9 (patch) | |
tree | 929e5549eb174f8892a58822b7ec2fedcce1614e /lib/Travel |
Initial Commit
Diffstat (limited to 'lib/Travel')
-rw-r--r-- | lib/Travel/Routing/DE/DBRIS.pm | 255 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/DBRIS/Connection.pm | 107 | ||||
-rw-r--r-- | lib/Travel/Routing/DE/DBRIS/Connection/Segment.pm | 79 |
3 files changed, 441 insertions, 0 deletions
diff --git a/lib/Travel/Routing/DE/DBRIS.pm b/lib/Travel/Routing/DE/DBRIS.pm new file mode 100644 index 0000000..aa4a476 --- /dev/null +++ b/lib/Travel/Routing/DE/DBRIS.pm @@ -0,0 +1,255 @@ +package Travel::Routing::DE::DBRIS; + +# vim:foldmethod=marker + +use strict; +use warnings; +use 5.020; +use utf8; + +use parent 'Class::Accessor'; + +use Carp qw(confess); +use DateTime; +use DateTime::Format::Strptime; +use Encode qw(decode encode); +use JSON; +use LWP::UserAgent; +use Travel::Status::DE::DBRIS; +use Travel::Routing::DE::DBRIS::Connection; + +our $VERSION = '0.01'; + +Travel::Routing::DE::DBRIS->mk_ro_accessors(qw(earlier later)); + +# {{{ Constructors + +sub new { + my ( $obj, %conf ) = @_; + my $service = $conf{service}; + + my $ua = $conf{user_agent}; + + if ( not $ua ) { + my %lwp_options = %{ $conf{lwp_options} // { timeout => 10 } }; + $ua = LWP::UserAgent->new(%lwp_options); + $ua->env_proxy; + } + + my $self = { + developer_mode => $conf{developer_mode}, + results => [], + from => $conf{from}, + to => $conf{to}, + ua => $ua, + }; + + bless( $self, $obj ); + + my $dt = $conf{datetime} // DateTime->now( time_zone => 'Europe/Berlin' ); + my @mots + = (qw(ICE EC_IC IR REGIONAL SBAHN BUS SCHIFF UBAHN TRAM ANRUFPFLICHTIG)); + if ( $conf{modes_of_transit} ) { + @mots = @{ $conf{modes_of_transit} // [] }; + } + + my $req = { + abfahrtsHalt => $conf{from}->id, + ankunftsHalt => $conf{to}->id, + anfrageZeitpunkt => $dt->strftime('%Y-%m-%dT%H:%M:00'), + ankunftSuche => 'ABFAHRT', + klasse => 'KLASSE_2', + produktgattungen => \@mots, + reisende => [ + { + typ => 'ERWACHSENER', + ermaessigungen => [ + { + art => 'KEINE_ERMAESSIGUNG', + klasse => 'KLASSENLOS' + }, + ], + alter => [], + anzahl => 1, + } + ], + schnelleVerbindungen => \1, + sitzplatzOnly => \0, + bikeCarriage => \0, + reservierungsKontingenteVorhanden => \0, + nurDeutschlandTicketVerbindungen => \0, + deutschlandTicketVorhanden => \0 + }; + + $self->{strptime_obj} //= DateTime::Format::Strptime->new( + pattern => '%Y-%m-%dT%H:%M:%S', + time_zone => 'Europe/Berlin', + ); + + $self->{strpdate_obj} //= DateTime::Format::Strptime->new( + pattern => '%Y-%m-%d', + time_zone => 'Europe/Berlin', + ); + + my $json = $self->{json} = JSON->new->utf8; + + if ( $conf{async} ) { + $self->{req} = $req; + return $self; + } + + if ( $conf{json} ) { + $self->{raw_json} = $conf{json}; + } + else { + my $req_str = $json->encode($req); + if ( $self->{developer_mode} ) { + say "requesting $req_str"; + } + + my ( $content, $error ) + = $self->post_with_cache( + 'https://www.bahn.de/web/api/angebote/fahrplan', $req_str ); + + if ($error) { + $self->{errstr} = $error; + return $self; + } + + if ( $self->{developer_mode} ) { + say decode( 'utf-8', $content ); + } + + $self->{raw_json} = $json->decode($content); + $self->parse_connections; + } + + return $self; +} + +sub new_p { + my ( $obj, %conf ) = @_; + my $promise = $conf{promise}->new; + + if ( + not( $conf{from} + and $conf{to} ) + ) + { + return $promise->reject('"from" and "to" opts are mandatory'); + } + + my $self = $obj->new( %conf, async => 1 ); + $self->{promise} = $conf{promise}; + + $self->post_with_cache_p( $self->{url} )->then( + sub { + my ($content) = @_; + $self->{raw_json} = $self->{json}->decode($content); + $self->parse_connections; + $promise->resolve($self); + return; + } + )->catch( + sub { + my ($err) = @_; + $promise->reject( $err, $self ); + return; + } + )->wait; + + return $promise; +} + +# }}} +# {{{ Internal Helpers + +sub post_with_cache { + my ( $self, $url, $req ) = @_; + my $cache = $self->{cache}; + + if ( $self->{developer_mode} ) { + say "POST $url $req"; + } + + if ($cache) { + my $content = $cache->thaw($url); + if ($content) { + if ( $self->{developer_mode} ) { + say ' cache hit'; + } + return ( ${$content}, undef ); + } + } + + if ( $self->{developer_mode} ) { + say ' cache miss'; + } + + my $reply = $self->{ua}->post( + $url, + Accept => 'application/json', + 'Content-Type' => 'application/json; charset=utf-8', + Origin => 'https://www.bahn.de', + Referer => 'https://www.bahn.de/buchung/fahrplan/suche', + 'Sec-Fetch-Dest' => 'empty', + 'Sec-Fetch-Mode' => 'cors', + 'Sec-Fetch-Site' => 'same-origin', + TE => 'trailers', + Content => $req, + ); + + if ( $reply->is_error ) { + say $reply->status_line; + return ( undef, $reply->status_line ); + } + my $content = $reply->content; + + if ($cache) { + $cache->freeze( $url, \$content ); + } + + return ( $content, undef ); +} + +sub post_with_cache_p { + ...; +} + +sub parse_connections { + my ($self) = @_; + + my $json = $self->{raw_json}; + + $self->{earlier} = $json->{verbindungReference}{earlier}; + $self->{later} = $json->{verbindungReference}{later}; + + for my $connection ( @{ $json->{verbindungen} // [] } ) { + push( + @{ $self->{connections} }, + Travel::Routing::DE::DBRIS::Connection->new( + json => $connection, + strpdate_obj => $self->{strpdate_obj}, + strptime_obj => $self->{strptime_obj} + ) + ); + } +} + +# }}} +# {{{ Public Functions + +sub errstr { + my ($self) = @_; + + return $self->{errstr}; +} + +sub connections { + my ($self) = @_; + return @{ $self->{connections} }; +} + +# }}} + +1; diff --git a/lib/Travel/Routing/DE/DBRIS/Connection.pm b/lib/Travel/Routing/DE/DBRIS/Connection.pm new file mode 100644 index 0000000..f516729 --- /dev/null +++ b/lib/Travel/Routing/DE/DBRIS/Connection.pm @@ -0,0 +1,107 @@ +package Travel::Routing::DE::DBRIS::Connection; + +use strict; +use warnings; +use 5.020; + +use parent 'Class::Accessor'; + +use DateTime::Duration; +use Travel::Routing::DE::DBRIS::Connection::Segment; + +our $VERSION = '0.01'; + +Travel::Routing::DE::DBRIS::Connection->mk_ro_accessors( + qw(changes + duration sched_duration rt_duration + sched_dep rt_dep dep + sched_arr rt_arr arr + occupancy occupancy_first occupancy_second) +); + +sub new { + my ( $obj, %opt ) = @_; + + my $json = $opt{json}; + my $strpdate = $opt{strpdate_obj}; + my $strptime = $opt{strptime_obj}; + + my $ref = { + changes => $json->{umstiegsAnzahl}, + id => $json->{tripId}, + strptime_obj => $strptime, + }; + + if ( my $d = $json->{verbindungsDauerInSeconds} ) { + $ref->{sched_duration} = DateTime::Duration->new( + hours => int( $d / 3600 ), + minutes => int( ( $d % 3600 ) / 60 ), + seconds => $d % 60, + ); + } + if ( my $d = $json->{ezVerbindungsDauerInSeconds} ) { + $ref->{rt_duration} = DateTime::Duration->new( + hours => int( $d / 3600 ), + minutes => int( ( $d % 3600 ) / 60 ), + seconds => $d % 60, + ); + } + $ref->{duration} = $ref->{rt_duration} // $ref->{sched_duration}; + + for my $occupancy ( @{ $json->{auslastungsmeldungen} // [] } ) { + if ( $occupancy->{klasse} eq 'KLASSE_1' ) { + $ref->{occupancy_first} = $occupancy->{stufe}; + } + if ( $occupancy->{klasse} eq 'KLASSE_2' ) { + $ref->{occupancy_second} = $occupancy->{stufe}; + } + } + + if ( $ref->{occupancy_first} and $ref->{occupancy_second} ) { + $ref->{occupancy} + = ( $ref->{occupancy_first} + $ref->{occupancy_second} ) / 2; + } + elsif ( $ref->{occupancy_first} ) { + $ref->{occupancy} = $ref->{occupancy_first}; + } + elsif ( $ref->{occupancy_second} ) { + $ref->{occupancy} = $ref->{occupancy_second}; + } + + for my $segment ( @{ $json->{verbindungsAbschnitte} // [] } ) { + push( + @{ $ref->{segments} }, + Travel::Routing::DE::DBRIS::Connection::Segment->new( + json => $segment, + strptime_obj => $strptime + ) + ); + } + + for my $key (qw(sched_dep rt_dep dep)) { + $ref->{$key} = $ref->{segments}[0]{$key}; + } + for my $key (qw(sched_arr rt_arr arr)) { + $ref->{$key} = $ref->{segments}[-1]{$key}; + } + + bless( $ref, $obj ); + + return $ref; +} + +sub segments { + my ($self) = @_; + + return @{ $self->{segments} // [] }; +} + +sub TO_JSON { + my ($self) = @_; + + my $ret = { %{$self} }; + + return $ret; +} + +1; diff --git a/lib/Travel/Routing/DE/DBRIS/Connection/Segment.pm b/lib/Travel/Routing/DE/DBRIS/Connection/Segment.pm new file mode 100644 index 0000000..b8134bb --- /dev/null +++ b/lib/Travel/Routing/DE/DBRIS/Connection/Segment.pm @@ -0,0 +1,79 @@ +package Travel::Routing::DE::DBRIS::Connection::Segment; + +use strict; +use warnings; +use 5.020; + +use parent 'Class::Accessor'; + +use DateTime::Duration; + +our $VERSION = '0.01'; + +Travel::Routing::DE::DBRIS::Connection::Segment->mk_ro_accessors( + qw( + dep_name dep_eva arr_name arr_eva + train train_long train_mid train_short direction + sched_dep rt_dep dep + sched_arr rt_arr arr + sched_duration rt_duration duration duration_percent + journey_id + ) +); + +sub new { + my ( $obj, %opt ) = @_; + + my $json = $opt{json}; + my $strptime = $opt{strptime_obj}; + + my $ref = { + arr_eva => $json->{ankunftsOrtExtId}, + arr_name => $json->{ankunftsOrt}, + dep_eva => $json->{abfahrtsOrtExtId}, + dep_name => $json->{abfahrtsOrt}, + train => $json->{verkehrsmittel}{name}, + train_short => $json->{verkehrsmittel}{kurzText}, + train_mid => $json->{verkehrsmittel}{mittelText}, + train_long => $json->{verkehrsmittel}{langText}, + direction => $json->{verkehrsmittel}{richtung}, + }; + + if ( my $ts = $json->{abfahrtsZeitpunkt} ) { + $ref->{sched_dep} = $strptime->parse_datetime($ts); + } + if ( my $ts = $json->{ezAbfahrtsZeitpunkt} ) { + $ref->{rt_dep} = $strptime->parse_datetime($ts); + } + $ref->{dep} = $ref->{rt_dep} // $ref->{sched_dep}; + + if ( my $ts = $json->{ankunftsZeitpunkt} ) { + $ref->{sched_arr} = $strptime->parse_datetime($ts); + } + if ( my $ts = $json->{ezAnkunftsZeitpunkt} ) { + $ref->{rt_arr} = $strptime->parse_datetime($ts); + } + $ref->{arr} = $ref->{rt_arr} // $ref->{sched_arr}; + + if ( my $d = $json->{abschnittsDauerInSeconds} ) { + $ref->{sched_duration} = DateTime::Duration->new( + hours => int( $d / 3600 ), + minutes => int( ( $d % 3600 ) / 60 ), + seconds => $d % 60, + ); + } + if ( my $d = $json->{ezAbschnittsDauerInSeconds} ) { + $ref->{rt_duration} = DateTime::Duration->new( + hours => int( $d / 3600 ), + minutes => int( ( $d % 3600 ) / 60 ), + seconds => $d % 60, + ); + } + $ref->{duration} = $ref->{rt_duration} // $ref->{sched_duration}; + + bless( $ref, $obj ); + + return $ref; +} + +1; |