package Travel::Status::DE::DBWagenreihung; use strict; use warnings; use 5.020; our $VERSION = '0.00'; use Carp qw(cluck confess); use JSON; use LWP::UserAgent; use Travel::Status::DE::DBWagenreihung::Section; use Travel::Status::DE::DBWagenreihung::Wagon; sub new { my ( $class, %opt ) = @_; if ( not $opt{train_number} ) { confess('train_number option must be set'); } if ( not $opt{departure} ) { confess('departure option must be set'); } my $self = { api_base => $opt{api_base} // 'https://www.apps-bahn.de/wr/wagenreihung/1.0', developer_mode => $opt{developer_mode}, cache => $opt{cache}, departure => $opt{departure}, json => JSON->new, serializable => $opt{serializable}, train_number => $opt{train_number}, user_agent => $opt{user_agent}, }; bless( $self, $class ); if ( not $self->{user_agent} ) { my %lwp_options = %{ $opt{lwp_options} // { timeout => 10 } }; $self->{user_agent} = LWP::UserAgent->new(%lwp_options); $self->{user_agent}->env_proxy; } $self->get_wagonorder; return $self; } sub get_wagonorder { my ($self) = @_; my $api_base = $self->{api_base}; my $cache = $self->{cache}; my $train_number = $self->{train_number}; my $datetime = $self->{departure}; if (ref($datetime) eq 'DateTime') { $datetime = $datetime->strftime('%Y%m%d%H%M'); } my ($content, $err) = $self->get_with_cache($cache, "${api_base}/${train_number}/${datetime}"); if ($err) { $self->{errstr} = "Failed to fetch station data: $err"; return; } my $json = $self->{json}->decode($content); if (exists $json->{error}) { $self->{errstr} = 'Backend error: ' . $json->{error}{msg}; return; } $self->{data} = $json->{data}; $self->{meta} = $json->{meta}; } sub error { my ($self) = @_; return $self->{errstr}; } sub sections { my ($self) = @_; if (exists $self->{sections}) { return @{$self->{sections}}; } for my $section (@{$self->{data}{istformation}{halt}{allSektor}}) { my $pos = $section->{positionamgleis}; push(@{$self->{sections}}, Travel::Status::DE::DBWagenreihung::Section->new( name => $section->{sektorbezeichnung}, start_percent => $pos->{startprozent}, end_percent => $pos->{endeprozent}, start_meters => $pos->{startmeter}, end_meters => $pos->{endemeter}, )); } return @{$self->{sections} // []}; } sub wagons { my ($self) = @_; if (exists $self->{wagons}) { return @{$self->{wagons}}; } for my $group (@{$self->{data}{istformation}{allFahrzeuggruppe}}) { for my $wagon (@{$group->{allFahrzeug}}) { push(@{$self->{wagons}}, Travel::Status::DE::DBWagenreihung::Wagon->new(%{$wagon})); } } @{$self->{wagons}} = sort { $a->{position}->{start_percent} <=> $b->{position}->{start_percent} } @{$self->{wagons}}; return @{$self->{wagons} // []}; } sub get_with_cache { my ( $self, $cache, $url ) = @_; if ( $self->{developer_mode} ) { say "GET $url"; } 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 $ua = $self->{user_agent}; my $res = $ua->get($url); if ( $res->is_error ) { return ( undef, $res->status_line ); } my $content = $res->decoded_content; if ($cache) { $cache->freeze( $url, \$content ); } return ( $content, undef ); } 1;