summaryrefslogtreecommitdiff
path: root/index.pl
diff options
context:
space:
mode:
Diffstat (limited to 'index.pl')
-rw-r--r--index.pl794
1 files changed, 8 insertions, 786 deletions
diff --git a/index.pl b/index.pl
index fb01343..467e0b2 100644
--- a/index.pl
+++ b/index.pl
@@ -1,790 +1,12 @@
#!/usr/bin/env perl
-use Mojolicious::Lite;
-use Cache::File;
-use File::Slurp qw(read_file write_file);
-use List::MoreUtils qw();
-use Travel::Status::DE::HAFAS;
-use Travel::Status::DE::HAFAS::StopFinder;
-use Travel::Status::DE::IRIS;
-use Travel::Status::DE::IRIS::Stations;
-use 5.014;
-use utf8;
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
-no if $] >= 5.018, warnings => 'experimental::smartmatch';
+use strict;
+use warnings;
-our $VERSION = qx{git describe --dirty} || '0.05';
+use lib 'lib';
+use Mojolicious::Commands;
-my $refresh_interval = 180;
-
-sub log_api_access {
- my $counter = 1;
- if ( -r $ENV{DBFAKEDISPLAY_STATS} ) {
- $counter = read_file( $ENV{DBFAKEDISPLAY_STATS} ) + 1;
- }
- write_file( $ENV{DBFAKEDISPLAY_STATS}, $counter );
- return;
-}
-
-sub get_results_for {
- my ( $backend, $station, %opt ) = @_;
-
- my $cache = Cache::File->new(
- cache_root => '/tmp/db-fakedisplay',
- default_expires => $refresh_interval . ' sec',
- lock_level => Cache::File::LOCK_LOCAL(),
- );
-
- # Cache::File has UTF-8 problems, so strip it (and any other potentially
- # problematic chars).
- my $cstation = $station;
- $cstation =~ tr{[0-9a-zA-Z -]}{}cd;
-
- my $cache_str = "${backend}_${cstation}";
-
- my $data = $cache->thaw($cache_str);
-
- if ( not $data ) {
- if ( $ENV{DBFAKEDISPLAY_STATS} ) {
- log_api_access();
- }
- if ( $backend eq 'iris' ) {
-
- # requests with DS100 codes should be preferred (they avoid
- # encoding problems on the IRIS server). However, only use them
- # if we have an exact match. Ask the backend otherwise.
- my @station_matches
- = Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @station_matches == 1 ) {
- $station = $station_matches[0][0];
- }
-
- my $status = Travel::Status::DE::IRIS->new(
- station => $station,
- serializable => 1,
- %opt
- );
- $data = {
- results => [ $status->results ],
- errstr => $status->errstr,
- };
- $cache->freeze( $cache_str, $data );
- }
- elsif ( $backend eq 'ris' ) {
- my $status = Travel::Status::DE::HAFAS->new(
- station => $station,
- excluded_mots => [qw[bus ferry ondemand tram u]],
- %opt
- );
- $data = {
- results => [ $status->results ],
- errstr => $status->errstr,
- };
- $cache->freeze( $cache_str, $data );
- }
- else {
- $data = {
- results => [],
- errstr => "Backend '$backend' not supported",
- };
- }
- }
-
- return $data;
-}
-
-helper 'handle_no_results' => sub {
- my ( $self, $backend, $station, $errstr ) = @_;
-
- if ( $backend eq 'ris' ) {
- my $db_service = Travel::Status::DE::HAFAS::get_service('DB');
- my $sf = Travel::Status::DE::HAFAS::StopFinder->new(
- url => $db_service->{stopfinder},
- input => $station,
- );
- my @candidates
- = map { [ $_->{name}, $_->{id} ] } $sf->results;
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0][1] ne $station ) )
- {
- $self->render(
- 'landingpage',
- stationlist => \@candidates,
- hide_opts => 0
- );
- return;
- }
- }
- if ( $backend eq 'iris' ) {
- my @candidates = map { [ $_->[1], $_->[0] ] }
- Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0][1] ne $station ) )
- {
- $self->render(
- 'landingpage',
- stationlist => \@candidates,
- hide_opts => 0
- );
- return;
- }
- }
- $self->render(
- 'landingpage',
- error => ( $errstr // "Got no results for '$station'" ),
- hide_opts => 0
- );
- return;
-};
-
-helper 'handle_no_results_marudor' => sub {
- my ( $self, $backend, $station, $errstr, $api_version, $callback ) = @_;
-
- $self->res->headers->access_control_allow_origin(q{*});
- my $json;
- if ($errstr) {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => $errstr,
- }
- );
- }
- elsif ( $backend eq 'iris' ) {
- my @candidates = map { { code => $_->[0], name => $_->[1] } }
- Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0]{code} ne $station ) )
- {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => 'ambiguous station code/name',
- candidates => \@candidates,
- }
- );
- }
- else {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => ( $errstr // "Got no results for '$station'" )
- }
- );
- }
- }
- else {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => ( $errstr // 'unknown station code/name' )
- }
- );
- }
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
- }
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
- }
- return;
-};
-
-helper 'is_important' => sub {
- my ( $self, $stop ) = @_;
-
- if ( $stop =~ m{ Hbf | Flughafen }ox ) {
- return 1;
- }
- return;
-};
-
-helper 'json_route_diff' => sub {
- my ( $self, $route, $sched_route ) = @_;
- my @json_route;
- my @route = @{$route};
- my @sched_route = @{$sched_route};
-
- my $route_idx = 0;
- my $sched_idx = 0;
-
- while ( $route_idx <= $#route and $sched_idx <= $#sched_route ) {
- if ( $route[$route_idx] eq $sched_route[$sched_idx] ) {
- push( @json_route, { name => $route[$route_idx] } );
- $route_idx++;
- $sched_idx++;
- }
-
- # this branch is inefficient, but won't be taken frequently
- elsif ( not( $route[$route_idx] ~~ \@sched_route ) ) {
- push(
- @json_route,
- {
- name => $route[$route_idx],
- isAdditional => 1
- }
- );
- $route_idx++;
- }
- else {
- push(
- @json_route,
- {
- name => $sched_route[$sched_idx],
- isCancelled => 1
- }
- );
- $sched_idx++;
- }
- }
- while ( $route_idx < $#route ) {
- push(
- @json_route,
- {
- name => $route[$route_idx],
- isAdditional => 1,
- isCancelled => 0
- }
- );
- $route_idx++;
- }
- while ( $sched_idx < $#sched_route ) {
- push(
- @json_route,
- {
- name => $sched_route[$sched_idx],
- isAdditional => 0,
- isCancelled => 1
- }
- );
- $sched_idx++;
- }
- return @json_route;
-};
-
-sub handle_request {
- my $self = shift;
- my $station = $self->stash('station');
- my $via = $self->stash('via');
-
- my @platforms = split( /,/, $self->param('platforms') // q{} );
- my @lines = split( /,/, $self->param('lines') // q{} );
- my $template = $self->param('mode') // 'clean';
- my $hide_low_delay = $self->param('hidelowdelay') // 0;
- my $hide_opts = $self->param('hide_opts') // 0;
- my $show_realtime = $self->param('show_realtime') // 0;
- my $backend = $self->param('backend') // 'iris';
- my $admode = $self->param('admode') // 'deparr';
- my $callback = $self->param('callback');
- my $apiver = $self->param('version') // 0;
- my %opt;
-
- my $api_version
- = $backend eq 'iris'
- ? $Travel::Status::DE::IRIS::VERSION
- : $Travel::Status::DE::HAFAS::VERSION;
-
- $self->stash( departures => [] );
- $self->stash( title => 'db-infoscreen' );
- $self->stash( version => $VERSION );
-
- if ( not( $template ~~ [qw[clean json marudor multi single]] ) ) {
- $template = 'clean';
- }
-
- if ( not $station ) {
- $self->render(
- 'landingpage',
- hide_opts => 0,
- show_intro => 1
- );
- return;
- }
-
- if ( $template eq 'marudor' and $backend eq 'iris' ) {
- $opt{lookahead} = 120;
- }
-
- my @departures;
- my $data = get_results_for( $backend, $station, %opt );
- my $results_ref = $data->{results};
- my $errstr = $data->{errstr};
- my @results = @{$results_ref};
-
- if ( not @results and $template ~~ [qw[json marudor]] ) {
- $self->handle_no_results_marudor( $backend, $station, $errstr,
- $api_version, $callback );
- return;
- }
-
- if ( not @results ) {
- $self->handle_no_results( $backend, $station, $errstr );
- return;
- }
-
- if ( $template eq 'single' ) {
- if ( not @platforms ) {
- for my $result (@results) {
- if ( not( $result->platform ~~ \@platforms ) ) {
- push( @platforms, $result->platform );
- }
- }
- @platforms = sort { $a <=> $b } @platforms;
- }
- my %pcnt;
- @results = grep { $pcnt{ $_->platform }++ < 1 } @results;
- @results = sort { $a->platform <=> $b->platform } @results;
- }
-
- if ( $backend eq 'iris' and $show_realtime ) {
- if ( $admode eq 'arr' ) {
- @results = sort {
- ( $a->arrival // $a->departure )
- <=> ( $b->arrival // $b->departure )
- } @results;
- }
- else {
- @results = sort {
- ( $a->departure // $a->arrival )
- <=> ( $b->departure // $b->arrival )
- } @results;
- }
- }
-
- for my $result (@results) {
- my $platform = ( split( / /, $result->platform ) )[0];
- my $line = $result->line;
- my $delay = $result->delay;
- if ( $via and $result->can('route') ) {
- my @route = $result->route;
- if ( $result->isa('Travel::Status::DE::IRIS::Result') ) {
- @route = $result->route_post;
- }
- if ( not( List::MoreUtils::any { m{$via}i } @route ) ) {
- next;
- }
- }
- if ( @platforms
- and not( List::MoreUtils::any { $_ eq $platform } @platforms ) )
- {
- next;
- }
- if ( @lines and not( List::MoreUtils::any { $line =~ m{^$_} } @lines ) )
- {
- next;
- }
- if ( $backend eq 'iris' and $admode eq 'arr' and not $result->arrival )
- {
- next;
- }
- if ( $backend eq 'iris'
- and $admode eq 'dep'
- and not $result->departure )
- {
- next;
- }
- my ( $info, $moreinfo );
- if ( $backend eq 'iris' ) {
- my $delaymsg
- = join( ', ', map { $_->[1] } $result->delay_messages );
- my $qosmsg = join( ' +++ ', map { $_->[1] } $result->qos_messages );
- if ( $result->is_cancelled ) {
- $info = "Fahrt fällt aus: ${delaymsg}";
- }
- elsif ( $result->delay and $result->delay > 0 ) {
- if ( $template eq 'clean' ) {
- $info = $delaymsg;
- $delay = $result->delay;
- }
- else {
- $info = sprintf( 'Verspätung ca. %d Min.%s%s',
- $result->delay, $delaymsg ? q{: } : q{}, $delaymsg );
- }
- }
- if ( $result->replacement_for and $template ne 'clean' ) {
- for my $rep ( $result->replacement_for ) {
- $info = sprintf(
- 'Ersatzzug für %s %s %s%s',
- $rep->type, $rep->train_no,
- $info ? '+++ ' : q{}, $info // q{}
- );
- }
- }
- if ( $info and $qosmsg ) {
- $info .= ' +++ ';
- }
- $info .= $qosmsg;
-
- if ( $result->additional_stops and not $result->is_cancelled ) {
- my $additional_line = join( q{, }, $result->additional_stops );
- $info
- = 'Zusätzliche Halte: '
- . $additional_line
- . ( $info ? ' +++ ' : q{} )
- . $info;
- if ( $template ne 'marudor' ) {
- push(
- @{$moreinfo},
- [ 'Zusätzliche Halte', $additional_line ]
- );
- }
- }
-
- if ( $result->canceled_stops and not $result->is_cancelled ) {
- my $cancel_line = join( q{, }, $result->canceled_stops );
- $info
- = 'Ohne Halt in: '
- . $cancel_line
- . ( $info ? ' +++ ' : q{} )
- . $info;
- if ( $template ne 'marudor' ) {
- push( @{$moreinfo}, [ 'Ohne Halt in', $cancel_line ] );
- }
- }
-
- push( @{$moreinfo}, $result->messages );
- }
- else {
- $info = $result->info;
- if ($info) {
- $moreinfo = [ [ 'HAFAS', $info ] ];
- }
- if ( $result->delay and $result->delay > 0 ) {
- if ($info) {
- $info = 'ca. +' . $result->delay . ': ' . $info;
- }
- else {
- $info = 'ca. +' . $result->delay;
- }
- }
- push( @{$moreinfo}, map { [ 'HAFAS', $_ ] } $result->messages );
- }
-
- my $time = $result->time;
-
- if ( $backend eq 'iris' ) {
-
- # ->time defaults to dep, so we only need to overwrite $time
- # if we want arrival times
- if ( $admode eq 'arr' ) {
- $time = $result->sched_arrival->strftime('%H:%M');
- }
-
- if ($show_realtime) {
- if ( ( $admode eq 'arr' and $result->arrival )
- or not $result->departure )
- {
- $time = $result->arrival->strftime('%H:%M');
- }
- else {
- $time = $result->departure->strftime('%H:%M');
- }
- }
- }
-
- if ( $template eq 'clean'
- and $info
- and $info =~ s{ (?: ca [.] \s* )? [+] (\d+) :? \s* }{}x )
- {
- $delay = $1;
- }
- if ( $hide_low_delay and $info ) {
- $info =~ s{ (?: ca [.] \s* )? [+] [ 1 2 3 4 ] $ }{}x;
- }
- if ($info) {
- $info =~ s{ (?: ca [.] \s* )? [+] (\d+) }{Verspätung ca $1 Min.}x;
- }
-
- if ( $template eq 'marudor' ) {
- my @json_route = $self->json_route_diff( [ $result->route ],
- [ $result->sched_route ] );
-
- if ( $apiver == 1 ) {
- push(
- @departures,
- {
- delay => $delay,
- destination => $result->destination,
- isCancelled => $result->can('is_cancelled')
- ? $result->is_cancelled
- : undef,
- messages => {
- delay => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->delay_messages
- ],
- qos => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->qos_messages
- ],
- },
- platform => $result->platform,
- route => \@json_route,
- scheduledPlatform => $result->sched_platform,
- time => $time,
- train => $result->train,
- via => [ $result->route_interesting(3) ],
- }
- );
- }
- else { # apiver == 2
- my ( $delay_arr, $delay_dep, $sched_arr, $sched_dep );
- if ( $result->arrival ) {
- $delay_arr = $result->arrival->subtract_datetime(
- $result->sched_arrival )->in_units('minutes');
- }
- if ( $result->departure ) {
- $delay_dep = $result->departure->subtract_datetime(
- $result->sched_departure )->in_units('minutes');
- }
- if ( $result->sched_arrival ) {
- $sched_arr = $result->sched_arrival->strftime('%H:%M');
- }
- if ( $result->sched_departure ) {
- $sched_dep = $result->sched_departure->strftime('%H:%M');
- }
- push(
- @departures,
- {
- delayArrival => $delay_arr,
- delayDeparture => $delay_dep,
- destination => $result->destination,
- isCancelled => $result->can('is_cancelled')
- ? $result->is_cancelled
- : undef,
- messages => {
- delay => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->delay_messages
- ],
- qos => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->qos_messages
- ],
- },
- platform => $result->platform,
- route => \@json_route,
- scheduledPlatform => $result->sched_platform,
- scheduledArrival => $sched_arr,
- scheduledDeparture => $sched_dep,
- train => $result->train,
- via => [ $result->route_interesting(3) ],
- }
- );
- }
- }
- elsif ( $backend eq 'iris' ) {
- push(
- @departures,
- {
- time => $time,
- sched_arrival => $result->sched_arrival
- ? $result->sched_arrival->strftime('%H:%M')
- : undef,
- sched_departure => $result->sched_departure
- ? $result->sched_departure->strftime('%H:%M')
- : undef,
- arrival => $result->arrival
- ? $result->arrival->strftime('%H:%M')
- : undef,
- departure => $result->departure
- ? $result->departure->strftime('%H:%M')
- : undef,
- train => $result->train,
- train_type => $result->type,
- train_line => $result->line_no,
- train_no => $result->train_no,
- via => [ $result->route_interesting(3) ],
- scheduled_route => [ $result->sched_route ],
- route_post => [ $result->route_post ],
- route_post_diff => [
- $self->json_route_diff(
- [ $result->route_post ],
- [ $result->sched_route_post ]
- )
- ],
- destination => $result->destination,
- origin => $result->origin,
- platform => $result->platform,
- scheduled_platform => $result->sched_platform,
- info => $info,
- is_cancelled => $result->is_cancelled,
- messages => {
- delay => [
- map { { timestamp => $_->[0], text => $_->[1] } }
- $result->delay_messages
- ],
- qos => [
- map { { timestamp => $_->[0], text => $_->[1] } }
- $result->qos_messages
- ],
- },
- moreinfo => $moreinfo,
- delay => $delay,
- additional_stops => [ $result->additional_stops ],
- canceled_stops => [ $result->canceled_stops ],
- replaced_by => [
- map { $_->type . q{ } . $_->train_no }
- $result->replaced_by
- ],
- replacement_for => [
- map { $_->type . q{ } . $_->train_no }
- $result->replacement_for
- ],
- }
- );
- }
- else {
- push(
- @departures,
- {
- time => $time,
- train => $result->train,
- train_type => $result->type,
- destination => $result->destination,
- platform => $platform,
- info => $info,
- is_cancelled => $result->can('is_cancelled')
- ? $result->is_cancelled
- : undef,
- messages => {
- delay => [],
- qos => [],
- },
- moreinfo => $moreinfo,
- delay => $delay,
- additional_stops => [],
- canceled_stops => [],
- replaced_by => [],
- replacement_for => [],
- }
- );
- }
- }
-
- if ( $template eq 'json' ) {
- $self->res->headers->access_control_allow_origin(q{*});
- my $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- preformatted => \@departures,
- version => $VERSION,
- raw => \@results,
- }
- );
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
- }
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
- }
- }
- elsif ( $template eq 'marudor' ) {
- $self->res->headers->access_control_allow_origin(q{*});
- my $json = $self->render_to_string(
- json => {
- departures => \@departures,
- }
- );
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
- }
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
- }
- }
- else {
- $self->render(
- $template,
- departures => \@departures,
- version => $VERSION,
- title => "departures for ${station}",
- refresh_interval => $refresh_interval + 3,
- hide_opts => $hide_opts,
- show_realtime => $show_realtime,
- );
- }
- return;
-}
-
-get '/_redirect' => sub {
- my $self = shift;
- my $station = $self->param('station');
- my $via = $self->param('via');
- my $params = $self->req->params;
-
- $params->remove('station');
- $params->remove('via');
-
- for my $param (qw(platforms)) {
- if ( not $params->param($param) ) {
- $params->remove($param);
- }
- }
-
- $params = $params->to_string;
-
- if ($via) {
- $self->redirect_to("/${station}/${via}?${params}");
- }
- else {
- $self->redirect_to("/${station}?${params}");
- }
-};
-
-app->defaults( layout => 'default' );
-
-get '/' => \&handle_request;
-get '/:station' => \&handle_request;
-get '/:station/:via' => \&handle_request;
-get '/multi/:station' => \&handle_request;
-
-app->config(
- hypnotoad => {
- accepts => 10,
- listen => ['http://*:8092'],
- pid_file => '/tmp/db-fakedisplay.pid',
- workers => $ENV{VRRFAKEDISPLAY_WORKERS} // 2,
- },
-);
-
-app->types->type( json => 'application/json; charset=utf-8' );
-app->plugin('browser_detect');
-app->start();
+Mojolicious::Commands->start_app('DBInfoscreen');