Group
Extension

App-Photobear/lib/App/Photobear.pm

#ABSTRACT: Photobear API client
package App::Photobear;
use v5.18;
use warnings;
use Carp;
use HTTP::Tiny;
use Data::Dumper;
use JSON::PP;

# Define version
our $VERSION = '0.1.2';

# Define constants
our $PHOTOBEAR_URL = 'https://photobear.io/api/public/submit-photo';
our @MODES = split / /, "background_removal vectorization super_resolution compress";
our $TESTMODE = $ENV{"PHOTOBEAR_TEST"} || 0;

# Export MODES
use Exporter qw(import);
our @EXPORT_OK = qw(loadconfig saveconfig url_exists curl photobear url_type @MODES);
our $TEST_ANSWER = q({"status":"success","data":{"result_url":"https://res.cloudinary.com/dy4s1umzd/image/upload/e_vectorize:colors:20:detail:0.7:corners:20/v1688570702/svg_inp/aia14r/core-people.svg"}});

sub loadconfig {
    my $filename = shift;
    if (! -e "$filename") {
        return {};
    }
    open my $fh, '<', $filename or Carp::croak "Can't open $filename: $!";
    my $config = {};
    while (my $line = readline($fh)) {
        chomp $line;
        next if $line =~ /^[#[]/;
        my ($key, $value) = split /=/, $line;
        $config->{"$key"} = $value;
    }
    return $config;
}

sub writeconfig {
    my ($filename, $config) = @_;
    open my $fh, '>', $filename or Carp::croak "Can't open $filename: $!";
    say $fh '[photobear]';
    foreach my $key (keys %$config) {
        print $fh "$key=$config->{$key}\n";
    }
}

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
        
        eval {
            require LWP::UserAgent;
            my $ua = LWP::UserAgent->new;
            $ua->ssl_opts(verify_hostname => 0);  # Disable SSL verification (optional)
            my $response = $ua->get($url);
            
            if ($response->is_success) {
                return 1;
            } else {
                return 0;
            }
        };
        if ($@) {
            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;
                }
            }
        }

    } else {
        return 0;
    }
}

sub url_type {
    my $url = shift;
    my $cmd = qq(curl --silent -L -I $url);
    if ($? == -1) {
        Carp::croak("[url_type] ", "Failed to execute: $!\n");
    } elsif ($? & 127) {
        Carp::croak("[url_type] ", sprintf("Child died with signal %d, %s coredump\n"),
            ($? & 127),  ($? & 128) ? 'with' : 'without');
    } elsif ($? >> 8) {
        Carp::croak("[url_type] ", sprintf("Child exited with value %d\n", $? >> 8));
    }
    my @output = `$cmd`;
    for my $line (@output) {
        chomp $line;
        if ($line =~ /^content-type/i) {
            # Strip color codes
            $line =~ s/\e\[[\d;]*[a-zA-Z]//g;
            my ($type) = $line =~ /Content-Type: (.*)/i;
            return $type;
        }
    }
    return undef;
}

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

    eval {
        require LWP::UserAgent;
        
        # Create a UserAgent object
        my $ua = LWP::UserAgent->new;
        $ua->ssl_opts(verify_hostname => 0);  # Disable SSL verification (optional)
        
        # Send the initial GET request
        my $response = $ua->get($url);
        
        # Follow redirects if any
        while ($response->is_redirect) {
            my $redirect_url = $response->header('Location');
            $response = $ua->get($redirect_url);
        }
        
        return $response->decoded_content;
    };
    
    if ($@) {
        # Fallback to system curl command
        eval {
            my $output = `curl --silent -L $url`;
            return $output;
        };
        if ($@) {
            die "Can't get content of $url: $@";
        }
    }
}

sub photobear {
    my ($api_key, $mode, $url) = @_;

    # If $mode is not in $MODES, then die
    if (! grep { $_ eq $mode } @MODES) {
        Carp::croak("Invalid mode: $mode (must be one of @MODES)");
    }

    # If no API key, then die
    if (! $api_key or length($api_key) == 0) {
        Carp::croak "No API key provided";
    }
    my $cmd = qq(curl --location --silent --request POST '$PHOTOBEAR_URL' \
        --header 'x-api-key: $api_key' \
        --header 'Content-Type: application/json' \
        --data-raw '{
            "photo_url":"$url", 
            "mode":"$mode"
        }');
    $cmd =~ s/\n//g;
    
    if ($ENV{'DEBUG'}) {
        say STDERR "[DEBUG] $cmd";
    }

    my $output = $ENV{'DEBUG'} ? $TEST_ANSWER : `$cmd`;
    if ($? == -1) {
        Carp::croak("[photobear]", "Failed to execute: $!\n");
    } elsif ($? & 127) {
        Carp::croak("[photobear]", sprintf("Child died with signal %d, %s coredump\n"),
            ($? & 127),  ($? & 128) ? 'with' : 'without');
    } elsif ($? >> 8) {
        Carp::croak("[photobear]", sprintf("Child exited with value %d\n", $? >> 8));
    }
    
    my $decoded_content = decode_json($output);
    return $decoded_content;

}

sub download {
    my ($url, $dest) = @_;
    # Use curl
    my $cmd = qq(curl  -L -o "$dest" "$url");#
    
    if ($TESTMODE) {
        return 1;
    }
    my $output = `$cmd`;
    if ($? == -1) {
        Carp::croak("[download] ", "Failed to execute: $!\n");
    } elsif ($? & 127) {
        Carp::croak("[download] ", sprintf("Child died with signal %d, %s coredump\n"),
            ($? & 127),  ($? & 128) ? 'with' : 'without');
    } elsif ($? >> 8) {
        Carp::croak("[download] ", sprintf("Child exited with value %d\n", $? >> 8));
    } elsif ($? == 0) {
        return 1;
    } else {
        return 0;
    }

}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::Photobear - Photobear API client

=head1 VERSION

version 0.1.2

=head1 SYNOPSIS

  use App::Photobear;
  
  # Load configuration from file
  my $config = App::Photobear::loadconfig($config_file);
  
  # Save configuration to file
  App::Photobear::saveconfig($config_file, $config);
  
  # Check if a URL exists
  my $url_exists = App::Photobear::url_exists($url);

  # Get the content of a URL
  my $content = App::Photobear::curl($url);

  # Perform Photobear API request
  my $result = App::Photobear::photobear($api_key, $mode, $url);

  # Download a file from a URL
  my $success = App::Photobear::download($url, $destination);

=head1 DESCRIPTION

App::Photobear is a Perl module that provides a client for the Photobear API. 
It includes functions to load and save configuration, check if a URL exists, perform API requests, and download files from URLs.

This script is meant to be used as a command-line tool, check L<photobear> for more information.

=head1 FUNCTIONS

=head2 loadconfig($filename)

Load configuration from the specified file. Returns a hash reference containing the configuration.

=head2 saveconfig($filename, $config)

Save the configuration to the specified file.

=head2 url_exists($url)

Check if the specified URL exists. Returns a boolean value indicating whether the URL exists or not.

=head2 curl($url)

Retrieve the content of the specified URL. Returns the content as a string.

=head2 photobear($api_key, $mode, $url)

Perform a request to the Photobear API with the given API key, mode, and URL. Returns the result as a hash reference.

=head2 download($url, $destination)

Download a file from the specified URL to the given destination path. Returns a boolean value indicating the success of the download operation.

=head1 VARIABLES

=head2 C<@MODES>

An array containing the supported modes for the Photobear API requests.

=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.