commit f7c0be73f02fe25f5f91a30e70c3188de621044a Author: vrag Date: Wed May 8 16:18:51 2019 +0300 Added uploadFile method diff --git a/lib/Mediafire/.Api.pm.swp b/lib/Mediafire/.Api.pm.swp new file mode 100644 index 0000000..d0fae70 Binary files /dev/null and b/lib/Mediafire/.Api.pm.swp differ diff --git a/lib/Mediafire/Api.pm b/lib/Mediafire/Api.pm new file mode 100644 index 0000000..3d29108 --- /dev/null +++ b/lib/Mediafire/Api.pm @@ -0,0 +1,187 @@ +package Mediafire::Api; + +use 5.008001; +use utf8; +use strict; +use warnings; +use open qw(:std :utf8); +use Carp qw/croak carp/; +use URI::Escape; +use LWP::UserAgent; +use File::Basename; +use HTTP::Request; +use JSON::XS; + +use Mediafire::Api::UploadFile; + +use Data::Printer; + +our $VERSION = '0.01'; + +############################ PRIVATE METHODS ############################################ + +my $openMainPage = sub { + # Open main page for set cookies + my ($self) = @_; + my $url = 'https://www.mediafire.com'; + my $res = $self->{ua}->get($url); + my $code = $res->code; + if ($code ne '200') { + croak "Wrong response code from url: '$url'. Code: $code"; + } + return 1; +}; + +my $getSessionToken = sub { + my ($self) = @_; + my $url = 'https://www.mediafire.com/apps/myfiles/?shared=0&multikeys=0'; + + my %headers = ( + 'referer' => 'https://www.mediafire.com/', + 'upgrade-insecure-requests' => '1', + ); + + my $res = $self->{ua}->get($url, %headers); + my $code = $res->code; + if ($code ne '200') { + croak "Can't get session_token by url: '$url'. Code: $code"; + } + my $body = $res->decoded_content; + if ($body =~ /token=(.+?)['&"]/) { + return $1; + } + croak "Can't found session_token from response. Url: '$url'"; +}; + +my $getLoginSecurityValue = sub { + my ($self) = @_; + my $ua = $self->{ua}; + my $url = 'https://www.mediafire.com/login/'; + + my $res = $ua->get($url); + my $code = $res->code; + if ($code ne '200') { + croak "Can't get login_security_value. Code: $code"; + } + if ($res->decoded_content =~ /<(input [^<>]*name="security".+?)>/) { + my $tag = $1; + if ($tag =~ /value="(.+?)"/) { + return $1; + } + } + croak "Can't find tag with name 'securyty' or can't get value of tag"; +}; + +######################################################################################## + +sub new { + my ($class) = @_; + my $self = {}; + bless $self, $class; + + $self->{ua} = LWP::UserAgent->new ( + agent => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36', + cookie_jar => {}, + ); + $self->{ua}->default_header('accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'); + $self->{ua}->default_header('accept-encoding' => 'gzip, deflate, br'); + $self->{ua}->default_header('accept-language' => 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7'); + + return $self; +} + +sub login { + my ($self, %opt) = @_; + $self->{login} = $opt{-login} // croak "You must specify '-login' param"; + $self->{password} = $opt{-password} // croak "You must specify '-password' param"; + + $self->$openMainPage(); + + my $security_value = $self->$getLoginSecurityValue(); + + my ($res, $code); + my $ua = $self->{ua}; + + my $url = 'https://www.mediafire.com/dynamic/client_login/mediafire.php'; + + my %param = ( + security => $security_value, + login_email => $self->{login}, + login_pass => $self->{password}, + login_remember => 'on', + ); + + my %headers = ( + 'accept-language' => 'ru,en-US;q=0.9,en;q=0.8', + 'cache-control' => 'max-age=0', + 'content-type' => 'application/x-www-form-urlencoded', + 'origin' => 'https://www.mediafire.com', + 'referer' => 'https://www.mediafire.com/login/', + 'upgrade-insecure-requests' => '1', + ); + + $res = $ua->post($url, \%param, %headers); + $code = $res->code; + if ($code ne '200') { + croak "Wrong response code on login url: '$url'. Code: $code"; + } + my $cookie = $res->header('set-cookie'); + # If logged in, server set 'session' cookie + if (not $cookie =~ /session=/) { + croak "Can't login to mediafire.com"; + } + + $self->renewSessionToken(); + + return 1; +} + +sub renewSessionToken { + my ($self) = @_; + + if (not $self->{session_token}) { + $self->{session_token} = $self->$getSessionToken(); + return 1; + } + + my %headers = ( + 'referer' => 'https://www.mediafire.com/widgets/notifications.php?event_load=1', + 'x-requested-with' => 'XMLHttpRequest', + ); + + my $url = 'https://www.mediafire.com/api/1.4/user/renew_session_token.php?r=jrko&session_token=' . $self->{session_token} . '&response_format=json'; + my $res = $self->{ua}->get($url, %headers); + my $code = $res->code; + if ($code ne '200') { + croak "Can't renew session token by url: '$url'. Code: $code"; + } + + my $json_res = eval { + decode_json($res->decoded_content); + }; + if ($@) { + croak "Can't decode response to json: $res->decoded_content"; + } + + my $response_result = $json_res->{response}->{result}; + if ($response_result eq 'Success') { + $self->{session_token} = $json_res->{response}->{session_token}; + return 1; + } + croak "Wrong result from response for renewSessionToken. Result: $response_result"; +} + +sub uploadFile { + my ($self, %opt) = @_; + + $self->renewSessionToken(); + my $upload_file = Mediafire::Api::UploadFile->new( + -ua => $self->{ua}, + -session_token => $self->{session_token}, + ); + $upload_file->uploadFile(%opt); + return $upload_file; +} + + +1; diff --git a/lib/Mediafire/Api/.UploadFile.pm.swp b/lib/Mediafire/Api/.UploadFile.pm.swp new file mode 100644 index 0000000..29a2734 Binary files /dev/null and b/lib/Mediafire/Api/.UploadFile.pm.swp differ diff --git a/lib/Mediafire/Api/UploadFile.pm b/lib/Mediafire/Api/UploadFile.pm new file mode 100644 index 0000000..972b4cf --- /dev/null +++ b/lib/Mediafire/Api/UploadFile.pm @@ -0,0 +1,230 @@ +package Mediafire::Api::UploadFile; + +use 5.008001; +use utf8; +use strict; +use warnings; +use open qw(:std :utf8); +use Carp qw/croak carp/; +use URI::Escape; +use LWP::UserAgent; +use LWP::ConnCache; +use File::Basename; +use HTTP::Request; +use JSON::XS; +use Crypt::Digest::SHA256 qw/sha256_hex/; +use Time::HiRes qw/gettimeofday/; +use IO::Socket::SSL; + +use Data::Printer; + +our $VERSION = '0.01'; + + +my $DEFAULT_BUFF_SIZE = 1048576; + +############################ PRIVATE METHODS ############################################ +my $getSha256Sum = sub { + my ($fname) = @_; + my $sha = Crypt::Digest::SHA256->new(); + $sha->addfile($fname); + return $sha->hexdigest; +}; + +my $checkUploadFile = sub { + my ($self) = @_; + + my $url = 'https://www.mediafire.com/api/1.5/upload/check.php'; + + my @sec = gettimeofday(); + my $microseconds = substr(join('', @sec), 0, 13); + + my %param = ( + 'hash' => $self->{file_hash}, + 'size' => $self->{file_size}, + 'filename' => $self->{basename}, + 'unit_size' => $self->{buff_size}, + 'resumable' => 'yes', + 'preemptive' => 'yes', + 'folder_key' => $self->{path}, + 'session_token' => $self->{session_token}, + 'response_format' => 'json', + $microseconds => '', + ); + + my $param_str = join('&', map {"$_=" . uri_escape($param{$_})} keys %param); + my $full_url = $url . '?' . $param_str; + my $res = $self->{ua}->get($full_url); + my $code = $res->code; + if ($code ne '200') { + croak "Wrong response code checkUploadFile(). Url: '$full_url'. Code: $code"; + } + my $json_res = eval { + decode_json($res->decoded_content); + }; + if ($@) { + croak "Can't parse respone '" . $res->decoded_content . "' to json"; + } + + # Get json response + my $response = $json_res->{response}; + if ($response->{result} ne 'Success') { + croak "checkUploadFile() not success"; + } + + # Limit exceeded + if ($response->{storage_limit_exceeded} ne 'no') { + croak "Can't checkUploadFile. Storage limit exceeded"; + } + + p $response; + + $self->{preemptive_quickkey} = $response->{preemptive_quickkey}; + $self->{upload_url} = $response->{upload_url}->{resumable}; + return 1; +}; + +my $checkResumeUpload = sub { + my ($self) = @_; + + my $ua = $self->{ua}; + my $url = 'https://ul.mediafireuserupload.com/api/1.5/upload/resumable.php'; + my %param = ( + 'session_token' => $self->{session_token}, + 'uploadkey' => $self->{path}, + 'response_format' => 'json', + ); + + my $headers = [ + 'Access-Control-Request-Method' => 'POST', + 'Origin' => 'https://www.mediafire.com', + 'Access-Control-Request-Headers' => 'content-type,x-filehash,x-filename,x-filesize,x-filetype,x-unit-hash,x-unit-id,x-unit-size', + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip, deflate, br', + 'Accept-Language' => 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7', + ]; + my $param_str = join('&', map {"$_=" . uri_escape($param{$_})} keys %param); + my $full_url = $url . '?' . $param_str; + my $request = HTTP::Request->new('OPTIONS', $full_url, $headers); + my $res = $ua->request($request); + my $code = $res->code; + if ($code ne '200') { + croak "Wrong response code on url: '$full_url'. Code: $code"; + } + + return 1; + +}; + +# Upload file +my $uploadF = sub { + my ($self) = @_; + + my $upload_file = $self->{upload_file}; + + my %param = ( + 'session_token' => $self->{session_token}, + 'uploadkey' => $self->{path}, + 'response_format' => 'json', + ); + my $param_str = join('&', map {"$_=" . uri_escape($param{$_})} keys %param); + my $url = $self->{upload_url} . '?' . $param_str; + + my $file_type = 'application/zip'; + my $unit_id = 0; + my $filebuf; + open my $FH, "<$upload_file" or croak "Can't open $upload_file $!"; + binmode $FH; + + my $json_res; + while (my $bytes = read($FH, $filebuf, $self->{buff_size})) { + my $unit_hash = sha256_hex($filebuf); + my @headers = ( + "Accept" => "*/*", + "Accept-Language" => "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", + "Accept-Encoding" => "gzip, deflate, br", + "Content-Type" => "application/octet-stream", + "Referer" => "https://www.mediafire.com/uploads", + "Origin" => "https://www.mediafire.com", + "X-Filesize" => $self->{file_size}, + "X-Filename" => $self->{basename}, + "X-Filetype" => $file_type, + "X-Filehash" => $self->{file_hash}, + "X-Unit-Hash" => $unit_hash, + "X-Unit-Size" => $bytes, + "X-Unit-Id" => $unit_id, + "Content" => $filebuf, + ); + my $res = $self->{ua}->post($url, @headers); + my $code = $res->code; + if ($code ne '200') { + croak "Wrong response code on request to url '$url'. Code: '$code'"; + } + + $json_res = eval { + decode_json($res->decoded_content); + }; + if ($@) { + croak "Can't decode response to json. Response: '" . $res->decoded_content . "'"; + } + + if ($json_res->{response}->{result} ne 'Success') { + croak "Response on url '$url' not success"; + } + + # Check all units ready + if ($json_res->{response}->{resumable_upload}->{all_units_ready} eq 'yes') { + last; + } + + ++$unit_id; + + } + close $FH; + + # Check all units ready + if ($json_res->{response}->{resumable_upload}->{all_units_ready} ne 'yes') { + croak "Not all parts of file '$upload_file' uploaded. Wrong answer from server"; + } + + return 1; +}; + +######################################################################################## + + +sub new { + my ($class, %opt) = @_; + my $self = {}; + $self->{ua} = $opt{-ua} // croak "You must specify param '-ua' for method new"; + $self->{session_token} = $opt{-session_token} // croak "You must specify '-session_token' param"; + $self->{buff_size} = $opt{-buff_zize} // $DEFAULT_BUFF_SIZE; + bless $self, $class; + return $self; +} + +sub uploadFile { + my ($self, %opt) = @_; + + $self->{upload_file} = $opt{'-file'} || croak "You must specify -file param for method uploadFile"; + $self->{path} = $opt{'-path'} || 'myfiles'; + + + if (not -f $self->{upload_file}) { + croak "File '" . $self->{upload_file} . "' not exists"; + } + + # Get all info about file + $self->{file_hash} = $getSha256Sum->($self->{upload_file}); + $self->{file_size} = -s $self->{upload_file}; + $self->{basename} = basename($self->{upload_file}); + + $self->$checkUploadFile(); + $self->$checkResumeUpload(); + $self->$uploadF(); +} + + + + +1; diff --git a/t/001_test.t b/t/001_test.t new file mode 100644 index 0000000..e3f83b2 --- /dev/null +++ b/t/001_test.t @@ -0,0 +1,79 @@ +# +#=============================================================================== +# +# FILE: 001_test.t +# +# DESCRIPTION: +# +# FILES: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: YOUR NAME (), +# ORGANIZATION: +# VERSION: 1.0 +# CREATED: 20.04.2019 21:40:06 +# REVISION: --- +#=============================================================================== + +use utf8; +use strict; +use warnings; +use File::Spec; + +my $CURR_DIR; +BEGIN { + $CURR_DIR = File::Spec->curdir; +} + +use lib File::Spec->catdir($CURR_DIR, './lib'); + +use Test::More 'no_plan'; # last test to print + +use_ok('Mediafire::Api'); + +my $LOGIN = $ENV{MEDIAFIRE_LOGIN}; +my $PASSWORD = $ENV{MEDIAFIRE_PASSWORD}; +my $UPLOAD_FILE = File::Spec->catfile($CURR_DIR, 't', 'test_upload3.f'); + + +SKIP: { + if (not $LOGIN) { + skip "Variable ENV{MEDIAFIRE_LOGIN} not set. Skip test"; + } + if (not $PASSWORD) { + skip "Variable ENV{MEDIAFIRE_PASSWORD} not set. Skip test"; + } + + # Login to mediafire + my $mediafire = eval { + testLogin($LOGIN, $PASSWORD); + }; + if ($@) { + skip $@; + } + + testUploadFile($mediafire, $UPLOAD_FILE); + +}; + + +sub testLogin { + my ($login, $password) = @_; + my $mediafire = Mediafire::Api->new(); + my $login_res = $mediafire->login( + -login => $login, + -password => $password, + ); + ok($login_res, 'Test login success'); + + return $mediafire; +} + +sub testUploadFile { + my ($mediafire, $file) = @_; + my $upload_file = $mediafire->uploadFile( + -file => $file, + ); +} + + diff --git a/t/test_upload3.f b/t/test_upload3.f new file mode 100644 index 0000000..347f4c6 Binary files /dev/null and b/t/test_upload3.f differ