Group
Extension

FR24-Bot/lib/FR24/Utils.pm

#ABSTRACT: Subroutines for FR24-Bot
use v5.12;
use warnings;
package FR24::Utils;
use JSON::PP;
use Exporter qw(import);
use HTTP::Tiny;
use File::Which;
# Export version
our @EXPORT = qw($VERSION);
our @EXPORT_OK = qw(loadconfig saveconfig url_exists authorized parse_flights systeminfo);

sub fr24_installed {
    my $cmd = qq(fr24feed-status);
    my $fr24feed_status = which($cmd);
    if (!defined $fr24feed_status) {
        return 0;
    } 
    return $fr24feed_status;
}
sub fr24_info {
    # [ ok ] FR24 Feeder/Decoder Process: running.
    # [ ok ] FR24 Stats Timestamp: 2023-07-13 06:39:30.
    # [ ok ] FR24 Link: connected [UDP].
    # [ ok ] FR24 Radar: T-EGSH204.
    # [ ok ] FR24 Tracked AC: 31.
    # [ ok ] Receiver: connected (28824914 MSGS/0 SYNC).
    # [ ok ] FR24 MLAT: ok [UDP].
    # [ ok ] FR24 MLAT AC seen: 27.
    my $info = {
        'radar' => 0,
        'seen'  => 0,
        'tracked' => 0,
        'connected' => 0,
        'running' => 0,
    };
    return $info if !fr24_installed();
    my $cmd = qq(fr24feed-status);
    my @output = `$cmd`;
    for my $line (@output) {
        chomp $line;
        if ($line =~ /FR24 Radar: (.*)/) {
            $info->{'radar'} = $1;
        }
        if ($line =~ /FR24 Tracked AC: (.*)/) {
            $info->{'tracked'} = $1;
        }
        if ($line =~ /FR24 Stats Timestamp: (.*)/) {
            $info->{'timestamp'} = $1;
        }
        if ($line =~ /FR24 Link: (.*)/) {
            $info->{'connected'} = $1;
        }
        if ($line =~ /FR24 MLAT AC seen: (.*)/) {
            $info->{'seen'} = $1;
        }
        if ($line =~ /FR24 Feeder\/Decoder Process: (.*)/) {
            $info->{'running'} = $1;
        }
    }
}
sub parse_flights {
    my ($json_text, $test) = @_;
    if (defined $test and $test > 0) {
      $json_text = '{"485789":["485789",51.94,0.9666,64.76496,38275,539,"6250",0,"","",1689143721,"","","",false,-1216,"KLM100"],"4067ef":["4067ef",0,0,0,37000,0,"0000",0,"","",1689143713,"","","",false,0,""],"4bb28f":["4bb28f",0,0,96.47746,19450,460,"4730",0,"","",1689143721,"","","",false,2240,""],"4cac55":["4cac55",0,0,0,34175,488,"3416",0,"","",1689143721,"","","",false,960,""],"3c5eee":["3c5eee",0,0,0,11775,0,"0000",0,"","",1689143665,"","","",false,0,""],"4ca848":["4ca848",51.35,1.024,90.472534,26025,482,"0572",0,"","",1689143719,"","","",false,-992,"RYR60UD"],"40775c":["40775c",53.42,-1.145,101.46763,23475,429,"3426",0,"","",1689143722,"","","",false,2112,"RUK000"],"406d4e":["406d4e",0,0,123.77186,16475,388,"6226",0,"","",1689143698,"","","",false,-1472,""],"4d21ee":["4d21ee",51.99,1.463,65.96107,25875,464,"3460",0,"","",1689143712,"","","",false,2176,"RYR000"],"4070e1":["4070e1",53.92,-1.082,139.22684,30100,478,"3446",0,"","",1689143721,"","","",false,2304,"EXS000"],"4791a0":["4791a0",51.94,1.264,73.30076,39225,512,"6241",0,"","",1689143722,"","","",false,640,"MDT000"],"4ca640":["4ca640",53.23,-0.6868,96.604836,34975,478,"4646",0,"","",1689143719,"","","",false,-64,"EIN000"],"4cadf4":["4cadf4",53.9,-0.5286,119.27368,37000,482,"3451",0,"","",1689143721,"","","",false,0,"RYR000"],"406d90":["406d90",0,0,0,21000,0,"3423",0,"","",1689143706,"","","",false,0,""],"4079f7":["4079f7",51.7,0.9323,263.7267,15700,276,"4632",0,"","",1689143707,"","","",false,-1536,"BAW000"],"4019f0":["4019f0",0,0,0,2300,0,"7000",0,"","",1689143721,"","","",false,0,""],"4076b1":["4076b1",52.36,0.4034,92.24087,32300,500,"4740",0,"","",1689143712,"","","",false,1184,"TOM000"],"4ca621":["4ca621",52.32,0.2067,100.06673,26950,451,"4653",0,"","",1689143722,"","","",false,1728,"RYR000"],"40769a":["40769a",52.24,1.311,99.09946,32450,500,"4741",0,"","",1689143722,"","","",false,896,"TOM000"],"3c6753":["3c6753",53.29,0.1518,279.62204,36000,413,"2544",0,"","",1689143722,"","","",false,0,"DLH000"],"40756e":["40756e",53.2,-0.1399,103.48089,25050,450,"6342",0,"","",1689143722,"","","",false,0,"EZY000"],"aaf968":["aaf968",53,1.002,96.21782,36950,518,"6315",0,"","",1689143721,"","","",false,-2560,"DAL000"],"40799b":["40799b",0,0,0,37700,0,"4447",0,"","",1689143719,"","","",false,0,""],"471f35":["471f35",0,0,276.65442,13275,241,"6605",0,"","",1689143689,"","","",false,-64,"WZZ000"],"485e30":["485e30",53.01,0.8713,110.196785,34850,503,"6251",0,"","",1689143722,"","","",false,-1344,"KLM000"],"ab4c1d":["ab4c1d",52.77,1.862,85.17478,27300,462,"6330",0,"","",1689143672,"","","",false,-960,"DAL000"],"3c6708":["3c6708",53.21,0.913,110.19787,43000,524,"2027",0,"","",1689143717,"","","",false,0,"DLH000"],"a4ffb7":["a4ffb7",0,0,98.704956,26850,420,"6312",0,"","",1689143674,"","","",false,-960,"DAL000"]}'; 
    }
    my $answer = {
        'status' => 'UNKNOWN',
        'id' => 0,
        'total' => 0,
        'uploaded' => 0,
        'data' => {},
        'raw' => {},
        'callsigns' => {},
    };

    if (length($json_text) == 0) {
        return $answer;
    }

    my $json = JSON::PP->new->utf8->pretty->canonical;
    my $json_data;
    eval {
        $json_data = $json->decode($json_text);
    };
    if ($@) {
        $answer->{'status'} = 'JSON_ERROR';
        return $answer;
    }

    $answer->{'status'} = 'OK';
    $answer->{'total'} = scalar keys %{$json_data} if defined $json_data;
    
    if (not defined $json_data) {
        return $answer;
    }
    for my $flight (sort keys %{$json_data}) {

       my $info = $json_data->{$flight};
       my $flight_hash = {
            'id'   => $flight,
            'lat'  => 0 + $info->[1],
            'long' => 0 +$info->[2],
            'alt'  => 0 + $info->[4],
            'callsign' => $info->[16],
       };
       #my $FLIGHT_ID = $flight;
       #if (length($info->[16]) > 0) {
       #     $answer->{'uploaded'}++;
       #     #TODO - check duplicates
       #     $FLIGHT_ID = $info->[16];
       #}
       
       $answer->{'data'}->{$flight} = $flight_hash;
       $answer->{'raw'}->{$flight} = $info;
       $answer->{'callsigns'}->{$info->[16]} = $flight if ( length($info->[16]) > 0 );
    }
    return $answer;
}



sub loadconfig {
    my $filename = shift;
    if (! -e "$filename") {
        return {};
    }
    open my $fh, '<', $filename or Carp::croak "Can't open $filename: $!";
    my $config = {
        'server' => { 
            'ip' => 'localhost',
        },
        'users' => {
            'everyone' => 1,
        },
    };

    my $section = "default";
    while (my $line = readline($fh)) {
        chomp $line;
        
        # Skip comment lines
        
        next if $line =~ /^#/;
        if ($line =~ /^\[(.*)\]$/) {
            $config->{lc("$1")} = {};
            $section = lc("$1");
            next;
        } elsif ($line =~/=/) {
            my ($key, $value) = split /=/, $line;
            $config->{"$section"}->{lc("$key")} = $value;
        }
        
    }
    return $config;
}

sub authorized {
    my ($config, $user) = @_;
    my $authorized = 0;
    return $authorized if !defined $user;
    return $authorized if $user !~ /^[0-9]+$/;
    # If there is no "users" section, everyone is authorized
    if (!defined $config->{'users'}) {
        print STDERR "[WARNING] Bad configuration file: no 'users' section\n";
        return 1;
    }
    if (defined $config->{'users'}->{'everyone'}) {
        $authorized = 1;
    }
    if (defined $config->{'users'}->{$user}  and $config->{'users'}->{$user} == 1 ) {
        $authorized = 1;
    }
    # Banned?
    if (defined $config->{'users'}->{$user}  and $config->{'users'}->{$user} == 0 ) {
        $authorized = 0;
    }
    return $authorized;
}
sub saveconfig {
    my ($filename, $config) = @_;
    open my $fh, '>', $filename or Carp::croak "Can't open $filename: $!";

    foreach my $section (keys %$config) {
        print $fh "[$section]\n";
        foreach my $key (keys %{$config->{$section}}) {
            my $value = $config->{$section}->{$key};
            print $fh "$key=$value\n";
        }
        print $fh "\n";
    }
    
    close $fh;
}

sub url_exists {
    my ($url) = @_;

    # Create an HTTP::Tiny object
    my $http = HTTP::Tiny->new;

    # Send a HEAD request to check the URL
    my $response = $http->head($url);
    
    # If the response status is success (2xx), the URL exists
    if ($response->{success}) {
        return 1;
    } elsif ($response->{status} == 599) {
        # Try anothe method: SSLeay 1.49 or higher required
        my $response = undef;
        eval {
            require LWP::UserAgent;
            my $ua = LWP::UserAgent->new;
            $ua->ssl_opts(verify_hostname => 0);  # Disable SSL verification (optional)
            $response = $ua->get($url);
             

            
        };
        if ($response->is_success) {
                return 1;
        } 
        
            
        my $cmd = qq(curl --silent -L -I "$url");
        my @output = `$cmd`;
        for my $line (@output) {
            chomp $line;
            if ($line =~ /^HTTP/ and $line =~ /200/) {
                return 1;
            }
        }
        return 0;

    } else {
        return 0;
    }
    
}

sub curl {
    my $url = shift;
    my $cmd = qq(curl --silent -L "$url");
    my @output = `$cmd`;
    if ($? != 0) {
        return undef;
    }
    return join("\n", @output);
}
sub systeminfo {
    my ($config) = @_;
    return {} if !defined $config->{'server'}->{'port'};
    return {} if !defined $config->{'server'}->{'ip'};

    my $url = $config->{'server'}->{'ip'} . ':' . $config->{'server'}->{'port'} . '/monitor.json';
    my $json_text = curl($url);
    if (!defined $json_text) {
        return {};
    }
    my $json_data;
    eval {
     my $json = JSON::PP->new->allow_nonref;
     $json_data = $json->decode($json_text);
    };
    if ($@) {
        return {};
    }
    return $json_data;

}
1;

__END__

=pod

=encoding UTF-8

=head1 NAME

FR24::Utils - Subroutines for FR24-Bot

=head1 VERSION

version 0.0.3

=head1 SYNOPSIS

    use FR24::Utils;


    my $result = FR24::Utils::parse_flights($json_text);

    # Load configuration from a file
    my $config = FR24::Utils::loadconfig("/path/to/config.ini");

    # Save configuration to a file
    FR24::Utils::saveconfig("/path/to/config.ini", $config);

    # Parse flights from JSON data
    my $json_text = '{"4067ef":["4067ef",0,0,0,37000,0,"0000",0,"","",1689143713,"","","",false,0,""]}';

=head1 DESCRIPTION

FR24::Utils provides utility functions used by FR24-Bot. The module contains 
methods to check the status of the FR24 server, parse flight data, manage 
configuration, authorize users, and other utility tasks.

=head1 FUNCTIONS

=head2 fr24_installed()

This function checks if FR24 is installed on the system. 

Returns 1 if installed, 0 otherwise.

=head2 fr24_info()

This function provides information about FR24. 

Returns a hash reference containing status information such as radar, tracked AC, stats timestamp, link status, etc.

=head2 parse_flights($json_text, $test)

This function takes a JSON string as input and parses it to extract flight information. 
The JSON string should contain flight data in a specific format. 

It returns a hash reference with the parsed information.

=head2 loadconfig($filename)

This function loads the configuration from a file. It returns a hash reference 
containing the configuration.

=head2 authorized($config, $user)

This function checks if a user is authorized. It takes a hash reference containing 
the configuration and a user name as input. It returns 1 if the user is authorized, 
0 otherwise.

=head2 saveconfig($filename, $config)

This function saves the configuration to a file. It takes a filename and a hash 
reference containing the configuration as input.

=head2 url_exists($url)

This function checks if a URL exists. It takes a URL as input and returns 1 if the 
URL exists, 0 otherwise.

=head1 EXPORTS

=head2 @EXPORT

Exports the $VERSION variable.

=head2 @EXPORT_OK

Exports the following functions: loadconfig, saveconfig, url_exists, authorized, 
parse_flights, systeminfo.

=head1 AUTHOR

Andrea Telatin <proch@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2023 by Andrea Telatin.

This is free software, licensed under:

  The MIT (X11) License

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.