305 lines
11 KiB
Perl
305 lines
11 KiB
Perl
package Peertube::DL::Downloaders;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use feature 'say';
|
|
|
|
use Symbol 'gensym';
|
|
use IPC::Open3;
|
|
use File::Basename;
|
|
use JSON;
|
|
use Data::Dumper;
|
|
use Mojo::DOM;
|
|
|
|
use Peertube::DL::Javascript;
|
|
use Peertube::DL::Utils;
|
|
|
|
sub youtube {
|
|
my $ua = shift;
|
|
my $response = shift;
|
|
my $options = shift;
|
|
my $dom = Mojo::DOM->new( $response->decoded_content );
|
|
my $script_tag = $dom->find('script')->grep(
|
|
sub {
|
|
$_[0]->text =~ /var ytInitialPlayerResponse =/;
|
|
}
|
|
)->first;
|
|
my ($ytInitialPlayerResponse) =
|
|
$script_tag->text =~ /^var ytInitialPlayerResponse = (.*?\});var meta/;
|
|
$ytInitialPlayerResponse = JSON::from_json($ytInitialPlayerResponse);
|
|
my $microformat =
|
|
$ytInitialPlayerResponse->{microformat}{playerMicroformatRenderer};
|
|
$ytInitialPlayerResponse = $ytInitialPlayerResponse->{streamingData};
|
|
|
|
if ( defined $options->{format} ) {
|
|
my $format = $options->{format};
|
|
($format) = grep { $_->{itag} eq $format } (
|
|
scalar @{ $ytInitialPlayerResponse->{adaptiveFormats} }
|
|
? @{ $ytInitialPlayerResponse->{adaptiveFormats} }
|
|
: (),
|
|
scalar @{ $ytInitialPlayerResponse->{formats} }
|
|
? @{ $ytInitialPlayerResponse->{formats} }
|
|
: ()
|
|
);
|
|
my $mime_type = $format->{mimeType} =~ s/;.*$//r;
|
|
my $extension = $mime_type =~ s/^.*?\///r =~ s/;.*$//r;
|
|
if ( defined $format->{url} ) {
|
|
return {
|
|
filename => $microformat->{title}{simpleText} . '.'
|
|
. $extension,
|
|
url => $format->{url},
|
|
};
|
|
}
|
|
my $url_data = $format->{signatureCipher};
|
|
$url_data = {
|
|
map {
|
|
my ( $a, $b ) = /(.*?)=(.*)$/;
|
|
(
|
|
$a => Peertube::DL::Utils::uri_decode(
|
|
Peertube::DL::Utils::uri_decode($b)
|
|
)
|
|
)
|
|
} split '&',
|
|
$url_data
|
|
};
|
|
|
|
my ($player_url) =
|
|
$response->decoded_content =~ m/"jsUrl"\s*:\s*("[^"]+")/;
|
|
$player_url = JSON::from_json( $player_url, { allow_nonref => 1 } );
|
|
$player_url = 'https://www.youtube.com' . $player_url
|
|
unless $player_url =~ m'^https://www.youtube.com';
|
|
say $player_url;
|
|
my $hostname = $player_url =~ s/https?:\/\///r;
|
|
$hostname = $hostname =~ s/\/.*$//;
|
|
$hostname = JSON::encode_json($hostname);
|
|
my $js = Peertube::DL::Javascript->new;
|
|
my $player_response = $ua->get($player_url);
|
|
my $regexes_search_function_name = [
|
|
qr/\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?<sig>(?:[a-zA-Z0-9]|\$)+)\(/,
|
|
qr/\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?<sig>(?:[a-zA-Z0-9]|\$)+)\(/,
|
|
qr/(?:\b|(?:[^a-zA-Z0-9]|\$))(?<sig>(?:[a-zA-Z0-9]|\$){2})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)/,
|
|
qr/(?<sig>(?:[a-zA-Z0-9]|\$)+)\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)/,
|
|
];
|
|
my $function_name_regen_sig;
|
|
for my $regex (@$regexes_search_function_name) {
|
|
($function_name_regen_sig) =
|
|
$player_response->decoded_content =~ /$regex/;
|
|
last if defined $function_name_regen_sig;
|
|
}
|
|
my ($extract_function_sig) = $player_response->decoded_content =~
|
|
m/\n(${function_name_regen_sig}=function\([a-zA-Z]+\)\{.*?\};)\n/;
|
|
my ($class_decoding) =
|
|
$extract_function_sig =~ /;([a-zA-Z]+)\.[a-zA-Z]+\(/;
|
|
my ($class_decoding_code) = $player_response->decoded_content =~
|
|
/;(var ${class_decoding}=\{.*?\}\};)/s;
|
|
$js->evalJS( join "\n", $class_decoding_code );
|
|
$js->evalJS( join "\n", $extract_function_sig );
|
|
my $signature =
|
|
$js->callJSFunction( $function_name_regen_sig, $url_data->{s} );
|
|
my $url = $url_data->{url} . "&sig=${signature}";
|
|
say $url;
|
|
return {
|
|
url => $url,
|
|
filename => $microformat->{title}{simpleText} . '.' . $extension,
|
|
};
|
|
} else {
|
|
my @formats = map {
|
|
{
|
|
id => $_->{itag},
|
|
mimeType => $_->{mimeType},
|
|
(
|
|
( defined $_->{averageBitrate} && defined $_->{bitrate} )
|
|
? ( bitrate => $_->{averageBitrate} // $_->{bitrate} )
|
|
: ()
|
|
),
|
|
(
|
|
( defined $_->{qualityLabel} )
|
|
? ( qualityLabel => $_->{qualityLabel} =~ s/p.*$//r )
|
|
: ()
|
|
),
|
|
(
|
|
( defined $_->{audioSampleRate} )
|
|
? ( audioSampleRate => $_->{audioSampleRate} )
|
|
: ()
|
|
),
|
|
(
|
|
( defined $_->{quality} )
|
|
? ( quality => $_->{quality} )
|
|
: ()
|
|
),
|
|
}
|
|
} (
|
|
scalar @{ $ytInitialPlayerResponse->{adaptiveFormats} }
|
|
? @{ $ytInitialPlayerResponse->{adaptiveFormats} }
|
|
: (),
|
|
scalar @{ $ytInitialPlayerResponse->{formats} }
|
|
? @{ $ytInitialPlayerResponse->{formats} }
|
|
: ()
|
|
);
|
|
my $video_formats = [
|
|
sort {
|
|
$b->{qualityLabel} <=> $a->{qualityLabel}
|
|
|| ( $b->{bitrate} // 0 ) <=> ( $a->{bitrate} // 0 )
|
|
} grep { defined $_->{qualityLabel} } @formats
|
|
];
|
|
my $audio_formats = [
|
|
sort {
|
|
$a->{audioSampleRate} <=> $b->{audioSampleRate}
|
|
|| $b->{bitrate} <=> $a->{bitrate}
|
|
} grep {
|
|
defined $_->{audioSampleRate}
|
|
&& $_->{mimeType} =~ /webm;/
|
|
} @formats
|
|
];
|
|
|
|
return {
|
|
options => { list_formats => 1 },
|
|
title => $microformat->{title}{simpleText},
|
|
description => $microformat->{description}{simpleText},
|
|
formats => {
|
|
video_formats => $video_formats,
|
|
audio_formats => $audio_formats,
|
|
},
|
|
|
|
};
|
|
}
|
|
}
|
|
|
|
sub kjanime {
|
|
my $ua = shift;
|
|
my $response = shift;
|
|
my $dom = Mojo::DOM->new( $response->decoded_content );
|
|
my $hotlink_span = $dom->find('.spoikj .ddserver')->grep(
|
|
sub {
|
|
my $i = shift;
|
|
$i->text =~ /HotLink/;
|
|
}
|
|
)->first;
|
|
my ($k_poi) = $hotlink_span->attr('onclick') =~ /getKpoi\((.*)\)/;
|
|
$k_poi =~ s/'//g;
|
|
my ( $t, $s, $l ) = ( split /,/, $k_poi );
|
|
my $url_get_php =
|
|
'https://kjanime.net/link/get.php?t='
|
|
. Peertube::DL::Utils::uri_encode($t) . '&s='
|
|
. Peertube::DL::Utils::uri_encode($s) . '&l='
|
|
. Peertube::DL::Utils::uri_encode($l);
|
|
my $get_php_response = $ua->get(
|
|
$url_get_php,
|
|
'X-Requested-With' => 'XMLHttpRequest',
|
|
);
|
|
$dom = Mojo::DOM->new( $get_php_response->decoded_content );
|
|
my $links = [ map { $_->attr('href') =~ s/^http:\/\//https:\/\//r }
|
|
@{ $dom->find('a')->to_array } ];
|
|
return { options => { list => 1 }, urls => $links };
|
|
}
|
|
|
|
sub kjanime_ch {
|
|
my $ua = shift;
|
|
my $response = shift;
|
|
my ($id) = $response->filename;
|
|
say $id;
|
|
my $param = join '',
|
|
map { Peertube::DL::Utils::uri_decode("%$_") } unpack( "(A2)*", $id );
|
|
say $param;
|
|
|
|
# FORMAT: KJA://70714B6E314943383135712B72582B70713768797A4B6E4D734D474775773D3D/1
|
|
# REQUEST: GET https://hotlink-api.kjanime.net/dl/serie.php?id=70714B6E314943383135712B72582B70713768797A4B6E4D734D474775773D3D&ep=1
|
|
$param =~ s/^KJA:\/\///;
|
|
my ( $real_id, $ep ) = split '/', $param;
|
|
my $url_serie_php =
|
|
'https://hotlink-api.kjanime.net/dl/serie.php?id='
|
|
. Peertube::DL::Utils::uri_encode($real_id) . '&ep='
|
|
. Peertube::DL::Utils::uri_encode($ep);
|
|
say $url_serie_php;
|
|
my $response_serie_php = $ua->get(
|
|
$url_serie_php,
|
|
'X-Requested-With' => 'XMLHttpRequest',
|
|
);
|
|
my $download_data =
|
|
JSON::decode_json( $response_serie_php->decoded_content );
|
|
say Data::Dumper::Dumper $download_data;
|
|
$download_data->{filename} = delete $download_data->{name};
|
|
$download_data->{url} = delete $download_data->{link};
|
|
return $download_data;
|
|
}
|
|
|
|
sub gocdn {
|
|
my $ua = shift;
|
|
my $response = shift;
|
|
my ($id) = $response->decoded_content =~ /gocdn\.html#(.+?)"/;
|
|
|
|
die "Id undefined" if !defined $id;
|
|
|
|
my $url_gocdn = 'https://streamium.xyz/gocdn.php?v=' . $id;
|
|
my $response_gocdn = $ua->get($url_gocdn);
|
|
|
|
die $response_gocdn->status_line . ' from ' . $url_gocdn
|
|
unless $response_gocdn->is_success;
|
|
|
|
my $google_url =
|
|
JSON::decode_json( $response_gocdn->decoded_content )->{file};
|
|
|
|
my ($filename) = $response->base =~ s/^.*\///gr . '.mp4';
|
|
|
|
return { url => $google_url, filename => $filename };
|
|
}
|
|
|
|
sub animeid {
|
|
my $ua = shift;
|
|
my $response = shift;
|
|
my @order = ( 'id', 'title', 'refer' );
|
|
my $url = $response->base;
|
|
my $url_ajax = 'https://animeid.to/ajax.php?';
|
|
my ($get_params) =
|
|
$response->decoded_content =~ /animeid\.to\/streaming\.php\?(.*?)"/;
|
|
$get_params = {
|
|
map {
|
|
my ( $key, $value ) = /^(.*?)=(.*)$/;
|
|
( $key, Peertube::DL::Utils::uri_decode($value) )
|
|
} split '&',
|
|
$get_params
|
|
};
|
|
say "Got from $url params for ajax:";
|
|
print Data::Dumper::Dumper $get_params;
|
|
say 'Adding passed url as refer.';
|
|
$get_params->{refer} = $url;
|
|
my $filename = $get_params->{title};
|
|
$url_ajax .= join '&',
|
|
map {
|
|
join '=',
|
|
( $_ => Peertube::DL::Utils::uri_encode( $get_params->{$_} ) )
|
|
} @order;
|
|
say 'Getting video location from: ' . $url_ajax;
|
|
my $ajax_data = $ua->get(
|
|
$url_ajax,
|
|
Referer => $url,
|
|
Host => 'animeid.to',
|
|
'X-Requested-With' => 'XMLHttpRequest',
|
|
)->decoded_content;
|
|
say "Decoding json... $ajax_data";
|
|
$ajax_data = JSON::decode_json $ajax_data;
|
|
die 'No video source found.' if ( !defined $ajax_data->{source} );
|
|
my $download_redirect_url = $ajax_data->{source}[0]{file}
|
|
// die "No url found.";
|
|
my $extension = $ajax_data->{source}[0]{type} // die "No extension found.";
|
|
$filename .= ".$extension";
|
|
say "Getting redirect to download url from $download_redirect_url...";
|
|
my $video_response = $ua->get(
|
|
$download_redirect_url,
|
|
Referer => $url,
|
|
Host => 'animeid.to',
|
|
);
|
|
if ( $video_response->is_redirect ) {
|
|
my $video_location = $video_response->header('Location');
|
|
die "No redirection." unless $video_location;
|
|
return {
|
|
url => $video_location,
|
|
filename => $filename,
|
|
};
|
|
} else {
|
|
die 'Getting redirect failed because: ' . $video_response->status_line;
|
|
}
|
|
}
|
|
1;
|