package CCClient; # (C) K D Tart / Barritel Ltd use strict; BEGIN { eval { require Cpanel::JSON::XS; return unless $Cpanel::JSON::XS::VERSION>=3; import Cpanel::JSON::XS; 1; } || eval { require JSON::XS; return unless $JSON::XS::VERSION>=2.3; import JSON::XS; 1; } || die "no JSON::XS or Cpanel::JSON::XS found"; }; use LWP::UserAgent; use HTTP::Request::Common; our $VERSION = 1.16; my $default_api_version = '0.1'; my $default_mode = 'test'; my %alt_modes = (1 => 'live', 0 => 'test'); my $serverURL_format = 'https://%s.apicontrol.co.uk/%s/%s'; our $AUTOLOAD; my $http_post = sub { # returns (response, error) my ($url, $data) = @_; return (undef, 'no data') if !length($data); my $ua = LWP::UserAgent->new(agent=>'Perl/'.__PACKAGE__.'/'.$VERSION); #$ua->timeout($request_timeout); my $result = $ua->request(POST $url, Content_Type=>'application/json', Content=>$data); return $result->content if $result->is_success; return (undef, $result->message); }; my $get_caller = sub { my $n = 0; while (++$n) { my ($pkg, $file, $line) = caller($n) or return undef; next if $pkg eq __PACKAGE__; return ($pkg, $file, $line); } }; my $croak = sub { # couldn't get Carp's croak to work in this context; it kept giving the following error and I couldn't # see why: "Undefined subroutine &Scalar::Util::blessed called at /usr/lib/perl5/5.8.8/overload.pm line 89." my @list = @_; if (!@list || $list[$#list] !~ /\n$/) { my ($pkg, $file, $line) = &$get_caller; push (@list, " at $file line $line\n"); }; die @list; }; my $args_hashref = sub { if (@_ == 1 && ref($_[0]) eq 'HASH') { return shift @_; } elsif ((scalar @_)%2) { &$croak('odd number of arguments; supply key=>val pairs'); } return {@_}; }; sub new { my ($class, $user, $pass, %opt) = @_; defined($user) || &$croak("Username not supplied"); defined($pass) || &$croak("Password not supplied"); my $mode = delete($opt{'mode'}) || $default_mode; my $apiver = delete($opt{'version'}) // $default_api_version; my ($excep) = grep { $_ } delete(@opt{'exceptions','RaiseError','raise_error'}); my $printerr = delete($opt{'PrintError'}); $mode =~ /^[\w-]+$/ || &$croak("Bad mode: $mode"); my $url = sprintf($serverURL_format, $alt_modes{$mode} || $mode, $apiver, 'json'); my $self = { auth => { username=>$user, password=>$pass, %opt }, url => $url, excep => $excep, printerr => $printerr, }; return bless $self, $class; } sub _last_error { my ($self) = @_; return $self->{'last_fail_result'}; } sub _login_as { my $self = shift @_; if (!@_) { delete($self->{auth}->{login_as}); } else { $self->{auth}->{login_as} = &$args_hashref(@_); } return $self; } sub AUTOLOAD { my $self = shift @_; $AUTOLOAD =~ /\:\:([^:]+)$/o || return undef; return if $1 eq 'DESTROY'; (my $ccfunc = $1) =~ s/_/\./og; my $args = (ref($_[0]) ? $_[0] : {@_}); # be flexible - accept hash or hashref, or send single scalar if we've got one my $json_req = encode_json({auth=>$self->{'auth'}, func=>$ccfunc, args=>$args}); my ($json_resp, $err) = &$http_post($self->{'url'}, $json_req); &$croak("HTTP error:$err") if $err; &$croak("no data returned")if !$json_resp; my $struct = eval { return decode_json($json_resp); } || &$croak("failed to decode response\nerror=${@}response follows:\n$json_resp"); if (!$struct->{'success'}) { $self->{'last_fail_result'} = $struct; if ($self->{'printerr'}) { my ($pkg, $file, $line) = &$get_caller; if ($self->{'printerr'} eq 'full' || $self->{'printerr'} > 1) { require Data::Dumper; import Data::Dumper; warn Dumper($struct), " at $file line $line\n"; } else { warn $struct->{'error'}, " at $file line $line\n"; } } &$croak($struct->{'error'}) if $self->{'excep'}; } return $struct; } 1;