Init
This commit is contained in:
311
src/YaHash.pm
Executable file
311
src/YaHash.pm
Executable file
@@ -0,0 +1,311 @@
|
|||||||
|
package YaHash;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
require Exporter;
|
||||||
|
use base qw/Exporter/;
|
||||||
|
|
||||||
|
our @EXPORT = qw/hash/;
|
||||||
|
|
||||||
|
sub M
|
||||||
|
{
|
||||||
|
my ($c, $b) = @_;
|
||||||
|
return ($c << $b) | ($c >> (32 - $b)); #>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
sub L
|
||||||
|
{
|
||||||
|
my ($x, $c) = @_;
|
||||||
|
|
||||||
|
my ($G, $b, $k, $F, $d);
|
||||||
|
|
||||||
|
$k = ($x & 2147483648);
|
||||||
|
$F = ($c & 2147483648);
|
||||||
|
$G = ($x & 1073741824);
|
||||||
|
$b = ($c & 1073741824);
|
||||||
|
$d = ($x & 1073741823) + ($c & 1073741823);
|
||||||
|
|
||||||
|
if ($G & $b)
|
||||||
|
{
|
||||||
|
return ($d ^ 2147483648 ^ $k ^ $F)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($G | $b)
|
||||||
|
{
|
||||||
|
if ($d & 1073741824)
|
||||||
|
{
|
||||||
|
return ($d ^ 3221225472 ^ $k ^ $F)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ($d ^ 1073741824 ^ $k ^ $F)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ($d ^ $k ^ $F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub r
|
||||||
|
{
|
||||||
|
my ($b, $d, $c) = @_;
|
||||||
|
|
||||||
|
return ($b & $d) | ((~$b) & $c);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub qz
|
||||||
|
{
|
||||||
|
my ($b, $d, $c) = @_;
|
||||||
|
|
||||||
|
return ($b & $c) | ($d & (~$c));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub p
|
||||||
|
{
|
||||||
|
my ($b, $d, $c) = @_;
|
||||||
|
|
||||||
|
return ($b ^ $d ^ $c);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub n
|
||||||
|
{
|
||||||
|
my ($b, $d, $c) = @_;
|
||||||
|
|
||||||
|
return ($d ^ ($b | (~$c)))
|
||||||
|
}
|
||||||
|
|
||||||
|
sub u
|
||||||
|
{
|
||||||
|
my ($G, $F, $ab, $aa, $k, $H, $I) = @_;
|
||||||
|
|
||||||
|
$G = L($G, L(L(r($F, $ab, $aa), $k), $I));
|
||||||
|
return L(M($G, $H), $F);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f
|
||||||
|
{
|
||||||
|
my ($G, $F, $ab, $aa, $k, $H, $I) = @_;
|
||||||
|
|
||||||
|
$G = L($G, L(L(qz($F, $ab, $aa), $k), $I));
|
||||||
|
return L(M($G, $H), $F);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub E
|
||||||
|
{
|
||||||
|
my ($G, $F, $ab, $aa, $k, $H, $I) = @_;
|
||||||
|
|
||||||
|
$G = L($G, L(L(p($F, $ab, $aa), $k), $I));
|
||||||
|
return L(M($G, $H), $F);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub t
|
||||||
|
{
|
||||||
|
my ($G, $F, $ab, $aa, $k, $H, $I) = @_;
|
||||||
|
|
||||||
|
$G = L($G, L(L(n($F, $ab, $aa), $k), $I));
|
||||||
|
return L(M($G, $H), $F);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub e
|
||||||
|
{
|
||||||
|
my $x = shift;
|
||||||
|
my $H;
|
||||||
|
my $k = length $x;
|
||||||
|
my $d = $k + 8;
|
||||||
|
my $c = ($d - ($d % 64)) / 64;
|
||||||
|
my $G = ($c + 1) * 16;
|
||||||
|
my @I = split //, (0 x ($G - 1));
|
||||||
|
my $b = 0;
|
||||||
|
my $F = 0;
|
||||||
|
|
||||||
|
|
||||||
|
while ($F < $k)
|
||||||
|
{
|
||||||
|
$H = ($F - ($F % 4)) / 4;
|
||||||
|
$b = ($F % 4) * 8;
|
||||||
|
$I[$H] = ($I[$H] | (ord(substr $x, $F, 1) << $b));
|
||||||
|
$F++
|
||||||
|
}
|
||||||
|
|
||||||
|
$H = ($F - ($F % 4)) / 4;
|
||||||
|
$b = ($F % 4) * 8;
|
||||||
|
$I[$H] = $I[$H] | (128 << $b);
|
||||||
|
$I[$G - 2] = $k << 3;
|
||||||
|
$I[$G - 1] = $k >> 29; #>>>
|
||||||
|
|
||||||
|
return @I;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub C
|
||||||
|
{
|
||||||
|
my $d = shift;
|
||||||
|
|
||||||
|
my $c = "";
|
||||||
|
my $k = "";
|
||||||
|
my ($x, $b);
|
||||||
|
|
||||||
|
for (my $b = 0; $b <= 3; $b++)
|
||||||
|
{
|
||||||
|
$x = ($d >> ($b * 8)) & 255; #>>>
|
||||||
|
$k = sprintf("%02X", $x);
|
||||||
|
$c = $c . substr($k, (length($k) - 2), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub z
|
||||||
|
{
|
||||||
|
return chr(shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub K
|
||||||
|
{
|
||||||
|
my $d = shift;
|
||||||
|
|
||||||
|
$d =~ s/\r\n/\n/g;
|
||||||
|
|
||||||
|
$d = z(498608 / 5666) . z(39523855 / 556674) . z(47450778 / 578668) . z(82156899 / 760712) . z(5026300 / 76156) . z(26011178 / 298979) . z(28319886 / 496840) . z(23477867 / 335398) . z(21650560 / 246029) . z(22521465 / 208532) . z(16067393 / 159083) . z(94458862 / 882793) . z(67654429 / 656839) . z(82331283 / 840115) . z(11508494 / 143856) . z(30221073 / 265097) . z(18712908 / 228206) . z(21423113 / 297543) . z(65168784 / 556998) . z(48924535 / 589452) . z(61018985 / 581133) . z(10644616 / 163763) . $d;
|
||||||
|
|
||||||
|
my $b = "";
|
||||||
|
for (my $x = 0; $x < length $d; $x++)
|
||||||
|
{
|
||||||
|
my $k = ord(substr $d, $x, 1);
|
||||||
|
if ($k < 128)
|
||||||
|
{
|
||||||
|
$b .= z($k);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (($k > 127) && ($k < 2048))
|
||||||
|
{
|
||||||
|
$b += z(($k >> 6) | 192);
|
||||||
|
$b += z(($k & 63) | 128)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$b += z(($k >> 12) | 224);
|
||||||
|
$b += z((($k >> 6) & 63) | 128);
|
||||||
|
$b += z(($k & 63) | 128)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub hash
|
||||||
|
{
|
||||||
|
my $s = shift;
|
||||||
|
|
||||||
|
|
||||||
|
my @D;
|
||||||
|
my ($Q, $h, $J, $v, $g, $Z, $Y, $X, $W);
|
||||||
|
|
||||||
|
my $T = 7;
|
||||||
|
my $R = 12;
|
||||||
|
my $O = 17;
|
||||||
|
my $N = 22;
|
||||||
|
my $B = 5;
|
||||||
|
my $A = 9;
|
||||||
|
my $y = 14;
|
||||||
|
my $w = 20;
|
||||||
|
my $o = 4;
|
||||||
|
my $m = 11;
|
||||||
|
my $l = 16;
|
||||||
|
my $j = 23;
|
||||||
|
my $V = 6;
|
||||||
|
my $U = 10;
|
||||||
|
my $S = 15;
|
||||||
|
my $P = 21;
|
||||||
|
|
||||||
|
$s = K($s);
|
||||||
|
@D = e($s);
|
||||||
|
$Z = 1732584193;
|
||||||
|
$Y = 4023233417;
|
||||||
|
$X = 2562383102;
|
||||||
|
$W = 271733878;
|
||||||
|
for ($Q = 0; $Q < scalar @D; $Q += 16)
|
||||||
|
{
|
||||||
|
$h = $Z;
|
||||||
|
$J = $Y;
|
||||||
|
$v = $X;
|
||||||
|
$g = $W;
|
||||||
|
$Z = u($Z, $Y, $X, $W, $D[$Q + 0], $T, 3614090360);
|
||||||
|
$W = u($W, $Z, $Y, $X, $D[$Q + 1], $R, 3905402710);
|
||||||
|
$X = u($X, $W, $Z, $Y, $D[$Q + 2], $O, 606105819);
|
||||||
|
$Y = u($Y, $X, $W, $Z, $D[$Q + 3], $N, 3250441966);
|
||||||
|
$Z = u($Z, $Y, $X, $W, $D[$Q + 4], $T, 4118548399);
|
||||||
|
$W = u($W, $Z, $Y, $X, $D[$Q + 5], $R, 1200080426);
|
||||||
|
$X = u($X, $W, $Z, $Y, $D[$Q + 6], $O, 2821735955);
|
||||||
|
$Y = u($Y, $X, $W, $Z, $D[$Q + 7], $N, 4249261313);
|
||||||
|
$Z = u($Z, $Y, $X, $W, $D[$Q + 8], $T, 1770035416);
|
||||||
|
$W = u($W, $Z, $Y, $X, $D[$Q + 9], $R, 2336552879);
|
||||||
|
$X = u($X, $W, $Z, $Y, $D[$Q + 10], $O, 4294925233);
|
||||||
|
$Y = u($Y, $X, $W, $Z, $D[$Q + 11], $N, 2304563134);
|
||||||
|
$Z = u($Z, $Y, $X, $W, $D[$Q + 12], $T, 1804603682);
|
||||||
|
$W = u($W, $Z, $Y, $X, $D[$Q + 13], $R, 4254626195);
|
||||||
|
$X = u($X, $W, $Z, $Y, $D[$Q + 14], $O, 2792965006);
|
||||||
|
$Y = u($Y, $X, $W, $Z, $D[$Q + 15], $N, 1236535329);
|
||||||
|
$Z = f($Z, $Y, $X, $W, $D[$Q + 1], $B, 4129170786);
|
||||||
|
$W = f($W, $Z, $Y, $X, $D[$Q + 6], $A, 3225465664);
|
||||||
|
$X = f($X, $W, $Z, $Y, $D[$Q + 11], $y, 643717713);
|
||||||
|
$Y = f($Y, $X, $W, $Z, $D[$Q + 0], $w, 3921069994);
|
||||||
|
$Z = f($Z, $Y, $X, $W, $D[$Q + 5], $B, 3593408605);
|
||||||
|
$W = f($W, $Z, $Y, $X, $D[$Q + 10], $A, 38016083);
|
||||||
|
$X = f($X, $W, $Z, $Y, $D[$Q + 15], $y, 3634488961);
|
||||||
|
$Y = f($Y, $X, $W, $Z, $D[$Q + 4], $w, 3889429448);
|
||||||
|
$Z = f($Z, $Y, $X, $W, $D[$Q + 9], $B, 568446438);
|
||||||
|
$W = f($W, $Z, $Y, $X, $D[$Q + 14], $A, 3275163606);
|
||||||
|
$X = f($X, $W, $Z, $Y, $D[$Q + 3], $y, 4107603335);
|
||||||
|
$Y = f($Y, $X, $W, $Z, $D[$Q + 8], $w, 1163531501);
|
||||||
|
$Z = f($Z, $Y, $X, $W, $D[$Q + 13], $B, 2850285829);
|
||||||
|
$W = f($W, $Z, $Y, $X, $D[$Q + 2], $A, 4243563512);
|
||||||
|
$X = f($X, $W, $Z, $Y, $D[$Q + 7], $y, 1735328473);
|
||||||
|
$Y = f($Y, $X, $W, $Z, $D[$Q + 12], $w, 2368359562);
|
||||||
|
$Z = E($Z, $Y, $X, $W, $D[$Q + 5], $o, 4294588738);
|
||||||
|
$W = E($W, $Z, $Y, $X, $D[$Q + 8], $m, 2272392833);
|
||||||
|
$X = E($X, $W, $Z, $Y, $D[$Q + 11], $l, 1839030562);
|
||||||
|
$Y = E($Y, $X, $W, $Z, $D[$Q + 14], $j, 4259657740);
|
||||||
|
$Z = E($Z, $Y, $X, $W, $D[$Q + 1], $o, 2763975236);
|
||||||
|
$W = E($W, $Z, $Y, $X, $D[$Q + 4], $m, 1272893353);
|
||||||
|
$X = E($X, $W, $Z, $Y, $D[$Q + 7], $l, 4139469664);
|
||||||
|
$Y = E($Y, $X, $W, $Z, $D[$Q + 10], $j, 3200236656);
|
||||||
|
$Z = E($Z, $Y, $X, $W, $D[$Q + 13], $o, 681279174);
|
||||||
|
$W = E($W, $Z, $Y, $X, $D[$Q + 0], $m, 3936430074);
|
||||||
|
$X = E($X, $W, $Z, $Y, $D[$Q + 3], $l, 3572445317);
|
||||||
|
$Y = E($Y, $X, $W, $Z, $D[$Q + 6], $j, 76029189);
|
||||||
|
$Z = E($Z, $Y, $X, $W, $D[$Q + 9], $o, 3654602809);
|
||||||
|
$W = E($W, $Z, $Y, $X, $D[$Q + 12], $m, 3873151461);
|
||||||
|
$X = E($X, $W, $Z, $Y, $D[$Q + 15], $l, 530742520);
|
||||||
|
$Y = E($Y, $X, $W, $Z, $D[$Q + 2], $j, 3299628645);
|
||||||
|
$Z = t($Z, $Y, $X, $W, $D[$Q + 0], $V, 4096336452);
|
||||||
|
$W = t($W, $Z, $Y, $X, $D[$Q + 7], $U, 1126891415);
|
||||||
|
$X = t($X, $W, $Z, $Y, $D[$Q + 14], $S, 2878612391);
|
||||||
|
$Y = t($Y, $X, $W, $Z, $D[$Q + 5], $P, 4237533241);
|
||||||
|
$Z = t($Z, $Y, $X, $W, $D[$Q + 12], $V, 1700485571);
|
||||||
|
$W = t($W, $Z, $Y, $X, $D[$Q + 3], $U, 2399980690);
|
||||||
|
$X = t($X, $W, $Z, $Y, $D[$Q + 10], $S, 4293915773);
|
||||||
|
$Y = t($Y, $X, $W, $Z, $D[$Q + 1], $P, 2240044497);
|
||||||
|
$Z = t($Z, $Y, $X, $W, $D[$Q + 8], $V, 1873313359);
|
||||||
|
$W = t($W, $Z, $Y, $X, $D[$Q + 15], $U, 4264355552);
|
||||||
|
$X = t($X, $W, $Z, $Y, $D[$Q + 6], $S, 2734768916);
|
||||||
|
$Y = t($Y, $X, $W, $Z, $D[$Q + 13], $P, 1309151649);
|
||||||
|
$Z = t($Z, $Y, $X, $W, $D[$Q + 4], $V, 4149444226);
|
||||||
|
$W = t($W, $Z, $Y, $X, $D[$Q + 11], $U, 3174756917);
|
||||||
|
$X = t($X, $W, $Z, $Y, $D[$Q + 2], $S, 718787259);
|
||||||
|
$Y = t($Y, $X, $W, $Z, $D[$Q + 9], $P, 3951481745);
|
||||||
|
$Z = L($Z, $h);
|
||||||
|
$Y = L($Y, $J);
|
||||||
|
$X = L($X, $v);
|
||||||
|
$W = L($W, $g)
|
||||||
|
}
|
||||||
|
|
||||||
|
my $i = C($Z) . C($Y) . C($X) . C($W);
|
||||||
|
|
||||||
|
return lc $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
402
src/ya.pl
Executable file
402
src/ya.pl
Executable file
@@ -0,0 +1,402 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Encode qw/from_to/;
|
||||||
|
use File::Basename;
|
||||||
|
use POSIX qw/strftime/;
|
||||||
|
use YaHash;
|
||||||
|
|
||||||
|
use constant IS_WIN => $^O eq 'MSWin32';
|
||||||
|
use constant
|
||||||
|
{
|
||||||
|
NL => IS_WIN ? "\015\012" : "\012",
|
||||||
|
TARGET_ENC => IS_WIN ? 'cp1251' : 'utf8',
|
||||||
|
TIMEOUT => 5,
|
||||||
|
AGENT => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0',
|
||||||
|
YANDEX_BASE => 'http://music.yandex.ru',
|
||||||
|
TRACK_URI_MASK => '/fragment/track/%d/album/%d?prefix=%s',
|
||||||
|
DOWNLOAD_INFO_MASK => '/xml/storage-proxy.xml?p=download-info/%s/2.mp3&nc=%d',
|
||||||
|
DOWNLOAD_PATH_MASK => 'http://%s/get-mp3/%s/%s?track-id=%s&from=service-10-track&similarities-experiment=default',
|
||||||
|
PLAYLIST_INFO_MASK => '/get/playlist2.xml?kinds=%d&owner=%s&r=%d',
|
||||||
|
PLAYLIST_TRACK_INFO_MASK => '/get/tracks.xml?tracks=%s',
|
||||||
|
ALBUM_INFO_MASK => '/fragment/album/%d?prefix=%s',
|
||||||
|
FILE_SAVE_EXT => '.mp3',
|
||||||
|
ARTIST_TITLE_DELIM => ' - ',
|
||||||
|
FACEGEN => POSIX::strftime('facegen-%Y-%m-%dT00-00-00', localtime)
|
||||||
|
};
|
||||||
|
use constant
|
||||||
|
{
|
||||||
|
DEBUG => 'DEBUG',
|
||||||
|
ERROR => 'ERROR',
|
||||||
|
INFO => 'INFO',
|
||||||
|
OK => 'OK'
|
||||||
|
};
|
||||||
|
|
||||||
|
my %log_colors =
|
||||||
|
(
|
||||||
|
&DEBUG => 'red on_white',
|
||||||
|
&ERROR => 'red',
|
||||||
|
&INFO => 'blue on_white',
|
||||||
|
&OK => 'green on_white'
|
||||||
|
);
|
||||||
|
|
||||||
|
my %req_modules =
|
||||||
|
(
|
||||||
|
NIX => [],
|
||||||
|
WIN => [ qw/Win32::Console::ANSI/ ],
|
||||||
|
ALL => [ qw/JSON::PP Getopt::Long::Descriptive Term::ANSIColor LWP::UserAgent HTTP::Cookies HTML::Entities/ ]
|
||||||
|
);
|
||||||
|
|
||||||
|
$\ = NL;
|
||||||
|
|
||||||
|
my @missing_modules;
|
||||||
|
for(@{$req_modules{ALL}}, IS_WIN ? @{$req_modules{WIN}} : @{$req_modules{NIX}})
|
||||||
|
{
|
||||||
|
eval "require $_";
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
($_) = $@ =~ /locate (.+?)(?:\.pm)? in \@INC/;
|
||||||
|
$_ =~ s/\//::/g;
|
||||||
|
push @missing_modules, $_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(@missing_modules)
|
||||||
|
{
|
||||||
|
print 'Please, install this modules: '.join ', ', @missing_modules;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($opt, $usage) = Getopt::Long::Descriptive::describe_options
|
||||||
|
(
|
||||||
|
basename(__FILE__).' %o',
|
||||||
|
['playlist|p:i', 'playlist id to download'],
|
||||||
|
['kind|k:s', 'playlist kind (eg. ya-playlist, music-blog, music-partners, etc.)'],
|
||||||
|
['album|a:i', 'album to download'],
|
||||||
|
['track|t:i', 'track to download (album id must be specified)'],
|
||||||
|
['dir|d:s', 'download path (current direcotry will be used by default)', {default => '.'}],
|
||||||
|
[],
|
||||||
|
['debug', 'print debug info during work'],
|
||||||
|
['help', 'print usage'],
|
||||||
|
[],
|
||||||
|
['Example: '],
|
||||||
|
["\t".basename(__FILE__).' -p 123 -k ya-playlist'],
|
||||||
|
["\t".basename(__FILE__).' -a 123'],
|
||||||
|
["\t".basename(__FILE__).' -a 123 -t 321']
|
||||||
|
);
|
||||||
|
|
||||||
|
if( $opt->help || ( !($opt->track && $opt->album) && !$opt->album && !($opt->playlist && $opt->kind) ) )
|
||||||
|
{
|
||||||
|
print $usage->text;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($opt->dir && !-d $opt->dir)
|
||||||
|
{
|
||||||
|
info(ERROR, 'Please, specify an existing directory');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $ua = LWP::UserAgent->new(agent => AGENT, cookie_jar => new HTTP::Cookies, timeout => TIMEOUT);
|
||||||
|
my $json_decoder = JSON::PP->new->utf8->pretty->allow_nonref;
|
||||||
|
$json_decoder->allow_singlequote(1);
|
||||||
|
|
||||||
|
|
||||||
|
if($opt->album || ($opt->playlist && $opt->kind))
|
||||||
|
{
|
||||||
|
my @track_list_info;
|
||||||
|
|
||||||
|
if($opt->track && $opt->album)
|
||||||
|
{
|
||||||
|
info(INFO, 'Fetching track info: '.$opt->track.' ['.$opt->album.']');
|
||||||
|
|
||||||
|
@track_list_info = get_single_track_info($opt->album, $opt->track);
|
||||||
|
}
|
||||||
|
elsif($opt->album)
|
||||||
|
{
|
||||||
|
info(INFO, 'Fetching album info: '.$opt->album);
|
||||||
|
|
||||||
|
@track_list_info = get_album_tracks_info($opt->album);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info(INFO, 'Fetching playlist info: '.$opt->playlist.' ['.$opt->kind.']');
|
||||||
|
|
||||||
|
@track_list_info = get_playlist_tracks_info($opt->playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(!@track_list_info)
|
||||||
|
{
|
||||||
|
info(ERROR, 'Can\'t get track list info');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $track_info_ref(@track_list_info)
|
||||||
|
{
|
||||||
|
fetch_track($track_info_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fetch_track
|
||||||
|
{
|
||||||
|
my $track_info_ref = shift;
|
||||||
|
|
||||||
|
fix_encoding(\$track_info_ref->{title});
|
||||||
|
$track_info_ref->{title} =~ s/\s+$//;
|
||||||
|
$track_info_ref->{title} =~ s/[\\\/:"*?<>|]+/-/g;
|
||||||
|
|
||||||
|
info(INFO, 'Trying to fetch track: '.$track_info_ref->{title});
|
||||||
|
|
||||||
|
my $track_url = get_track_url($track_info_ref->{dir});
|
||||||
|
if(!$track_url)
|
||||||
|
{
|
||||||
|
info(ERROR, 'Can\'t get track url');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $file_path = download_track($track_url, $track_info_ref->{title});
|
||||||
|
if(!$file_path)
|
||||||
|
{
|
||||||
|
info(ERROR, 'Failed to download track');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info(OK, 'Saved track at '.$file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub download_track
|
||||||
|
{
|
||||||
|
my ($url, $title) = @_;
|
||||||
|
|
||||||
|
my $request = $ua->get($url);
|
||||||
|
if(!$request->is_success)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Request failed in download_track');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $web_data_size = $request->headers->{'content-length'};
|
||||||
|
|
||||||
|
my $file_path = $opt->dir.'/'.$title.FILE_SAVE_EXT;
|
||||||
|
if(open(F, '>', $file_path))
|
||||||
|
{
|
||||||
|
# Awkward moment
|
||||||
|
undef $\;
|
||||||
|
|
||||||
|
binmode F;
|
||||||
|
print F $request->content;
|
||||||
|
close F;
|
||||||
|
|
||||||
|
$\ = NL;
|
||||||
|
|
||||||
|
my $disk_data_size = -s $file_path;
|
||||||
|
|
||||||
|
if($web_data_size && $disk_data_size != $web_data_size)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Actual file size differs from expected ('.$disk_data_size.'/'.$web_data_size.')');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
info(DEBUG, 'Failed to open file '.$file_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_track_url
|
||||||
|
{
|
||||||
|
my $storage_dir = shift;
|
||||||
|
|
||||||
|
my $request = $ua->get(YANDEX_BASE.sprintf(DOWNLOAD_INFO_MASK, $storage_dir, time));
|
||||||
|
if(!$request->is_success)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Request failed in get_track_url');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my %fields = (host => '', path => '', ts => '', region => '', s => '');
|
||||||
|
|
||||||
|
for my $key(keys %fields)
|
||||||
|
{
|
||||||
|
if($request->as_string =~ /<$key>(.+?)<\/$key>/)
|
||||||
|
{
|
||||||
|
$fields{$key} = $1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Failed to parse '.$key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $hash = hash(substr($fields{path}, 1) . $fields{s});
|
||||||
|
|
||||||
|
my $url = sprintf(DOWNLOAD_PATH_MASK, $fields{host}, $hash, $fields{ts}.$fields{path}, (split /\./, $storage_dir)[1]);
|
||||||
|
|
||||||
|
info(DEBUG, 'Track url: '.$url);
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_single_track_info
|
||||||
|
{
|
||||||
|
my ($album_id, $track_id) = @_;
|
||||||
|
|
||||||
|
my $request = $ua->get(YANDEX_BASE.sprintf(TRACK_URI_MASK, $track_id, $album_id, FACEGEN));
|
||||||
|
if(!$request->is_success)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Request failed in get_single_track_info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($json_data) = ($request->as_string =~ /data-from="track" onclick=["']return (.+?)["']>/);
|
||||||
|
if(!$json_data)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Can\'t parse JSON blob');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTML::Entities::decode_entities($json_data);
|
||||||
|
|
||||||
|
my $json;
|
||||||
|
eval
|
||||||
|
{
|
||||||
|
$json = $json_decoder->decode($json_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Error decoding json '.$@);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {dir => $json->{storage_dir}, title => $json->{artist}.ARTIST_TITLE_DELIM.$json->{title}};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_album_tracks_info
|
||||||
|
{
|
||||||
|
my $album_id = shift;
|
||||||
|
|
||||||
|
my $request = $ua->get(YANDEX_BASE.sprintf(ALBUM_INFO_MASK, $album_id, FACEGEN));
|
||||||
|
if(!$request->is_success)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Request failed in get_album_tracks_info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($json_data) = ($request->as_string =~ /data-from="album-whole" onclick="return (.+?)"><a/);
|
||||||
|
if(!$json_data)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Can\'t parse JSON blob');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTML::Entities::decode_entities($json_data);
|
||||||
|
|
||||||
|
my $json;
|
||||||
|
eval
|
||||||
|
{
|
||||||
|
$json = $json_decoder->decode($json_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Error decoding json '.$@);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my $title = $json->{title};
|
||||||
|
if(!$title)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Can\'t get album title');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_encoding(\$title);
|
||||||
|
|
||||||
|
info(INFO, 'Album title: '.$title);
|
||||||
|
info(INFO, 'Tracks total: '. $json->{track_count});
|
||||||
|
|
||||||
|
|
||||||
|
return map { { dir => $_->{storage_dir}, title=> $_->{artist}.ARTIST_TITLE_DELIM.$_->{title} } } @{$json->{tracks}};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_playlist_tracks_info
|
||||||
|
{
|
||||||
|
my $playlist_id = shift;
|
||||||
|
|
||||||
|
my $request = $ua->get(YANDEX_BASE.sprintf(PLAYLIST_INFO_MASK, $playlist_id, $opt->kind, time));
|
||||||
|
if(!$request->is_success)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Request failed in get_playlist_tracks_info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $json_data = $request->content;
|
||||||
|
|
||||||
|
HTML::Entities::decode_entities($json_data);
|
||||||
|
|
||||||
|
my $json;
|
||||||
|
eval
|
||||||
|
{
|
||||||
|
$json = $json_decoder->decode($json_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Error decoding json '.$@);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my $title = $json->{playlists}[0]->{title};
|
||||||
|
if(!$title)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Can\'t get playlist title');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_encoding(\$title);
|
||||||
|
|
||||||
|
info(INFO, 'Playlist title: '.$title);
|
||||||
|
info(INFO, 'Tracks total: '. scalar @{$json->{playlists}[0]->{tracks}});
|
||||||
|
|
||||||
|
|
||||||
|
$request = $ua->get(YANDEX_BASE.sprintf(PLAYLIST_TRACK_INFO_MASK, join(',', @{$json->{playlists}[0]->{tracks}})));
|
||||||
|
if(!$request->is_success)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Request failed in get_playlist_tracks_info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eval
|
||||||
|
{
|
||||||
|
$json = $json_decoder->decode($request->content);
|
||||||
|
};
|
||||||
|
|
||||||
|
if($@)
|
||||||
|
{
|
||||||
|
info(DEBUG, 'Error decoding json '.$@);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return map { { dir => $_->{storage_dir}, title=> $_->{artist}.ARTIST_TITLE_DELIM.$_->{title} } } @{$json->{tracks}};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fix_encoding
|
||||||
|
{
|
||||||
|
my $ref = shift;
|
||||||
|
from_to($$ref, 'unicode', TARGET_ENC);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub info
|
||||||
|
{
|
||||||
|
my ($type, $msg) = @_;
|
||||||
|
|
||||||
|
return if !$opt->debug && $type eq DEBUG;
|
||||||
|
|
||||||
|
print Term::ANSIColor::colored('['.$type.']', $log_colors{$type}), ' ', $msg;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user