156 lines
3.9 KiB
Perl
156 lines
3.9 KiB
Perl
package YTMusicAPI::Parsers::Songs;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Exporter 'import';
|
|
use Regexp::Common qw(number);
|
|
|
|
use YTMusicAPI::Parsers::Utils;
|
|
use YTMusicAPI::Navigation;
|
|
|
|
sub parse_song_artists {
|
|
my ( $data, $index ) = @_;
|
|
my $flex_item = get_flex_column_item( $data, $index );
|
|
|
|
if ( !$flex_item ) {
|
|
return undef;
|
|
}
|
|
else {
|
|
my $runs = $flex_item->{"text"}{"runs"};
|
|
return parse_song_artists_runs($runs);
|
|
}
|
|
}
|
|
|
|
sub parse_song_artists_runs {
|
|
my ($runs) = @_;
|
|
my @artists = ();
|
|
|
|
for my $j ( 0 .. int( @$runs / 2 ) ) {
|
|
push @artists,
|
|
{
|
|
"name" => $runs->[ $j * 2 ]{"text"},
|
|
"id" => nav( $runs->[ $j * 2 ], $NAVIGATION_BROWSE_ID, 1 )
|
|
};
|
|
}
|
|
return \@artists;
|
|
}
|
|
|
|
sub parse_song_runs {
|
|
my ($runs) = @_;
|
|
my %parsed = ( "artists" => [] );
|
|
my $i = 0;
|
|
|
|
for my $run (@$runs) {
|
|
if ( $i % 2 ) { # uneven items are always separators
|
|
$i++;
|
|
next;
|
|
}
|
|
|
|
my $text = $run->{"text"};
|
|
|
|
if ( exists $run->{"navigationEndpoint"} ) { # artist or album
|
|
my $item = {
|
|
"name" => $text,
|
|
"id" => nav( $run, $NAVIGATION_BROWSE_ID, 1 )
|
|
};
|
|
|
|
if (
|
|
$item->{"id"}
|
|
&& ( $item->{"id"} =~ /^MPRE/
|
|
|| $item->{"id"} =~ /release_detail/ )
|
|
)
|
|
{ # album
|
|
$parsed{"album"} = $item;
|
|
}
|
|
else { # artist
|
|
push @{ $parsed{"artists"} }, $item;
|
|
}
|
|
|
|
}
|
|
else {
|
|
# note: YT uses non-breaking space \xa0 to separate number and magnitude
|
|
if ( $text =~ /^\d([^ ])* [^ ]*$/ && $i > 0 ) {
|
|
( $parsed{"views"} ) = split( / /, $text, 2 );
|
|
}
|
|
elsif ( $text =~ /^(\d+:)*\d+:\d+$/ ) {
|
|
$parsed{"duration"} = $text;
|
|
$parsed{"duration_seconds"} = parse_duration($text);
|
|
}
|
|
elsif ( $text =~ /^\d{4}$/ ) {
|
|
$parsed{"year"} = $text;
|
|
}
|
|
else { # artist without id
|
|
push @{ $parsed{"artists"} },
|
|
{ "name" => $text, "id" => undef };
|
|
}
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return \%parsed;
|
|
}
|
|
|
|
sub parse_song_album {
|
|
my ( $data, $index ) = @_;
|
|
my $flex_item = get_flex_column_item( $data, $index );
|
|
my $browse_id = nav( $flex_item, $TEXT_RUN + $NAVIGATION_BROWSE_ID, 1 );
|
|
return !$flex_item
|
|
? undef
|
|
: { "name" => get_item_text( $data, $index ), "id" => $browse_id };
|
|
}
|
|
|
|
sub parse_song_library_status {
|
|
my ($item) = @_;
|
|
my $library_status =
|
|
nav( $item, [ $TOGGLE_MENU, "defaultIcon", "iconType" ], 1 );
|
|
return $library_status eq "LIBRARY_SAVED";
|
|
}
|
|
|
|
sub parse_song_menu_tokens {
|
|
my ($item) = @_;
|
|
my $toggle_menu = $item->{$TOGGLE_MENU};
|
|
|
|
my $library_add_token =
|
|
nav( $toggle_menu, [ "defaultServiceEndpoint", $FEEDBACK_TOKEN ], 1 );
|
|
my $library_remove_token =
|
|
nav( $toggle_menu, [ "toggledServiceEndpoint", $FEEDBACK_TOKEN ], 1 );
|
|
|
|
my $in_library = parse_song_library_status($item);
|
|
if ($in_library) {
|
|
my $tmp = $library_remove_token;
|
|
$library_add_token = $library_remove_token;
|
|
$library_remove_token = $tmp;
|
|
}
|
|
|
|
return { "add" => $library_add_token, "remove" => $library_remove_token };
|
|
}
|
|
|
|
sub parse_like_status {
|
|
my ($service) = @_;
|
|
my @status = ( "LIKE", "INDIFFERENT" );
|
|
|
|
my $status_index = 0;
|
|
for my $i ( 0 .. $#status ) {
|
|
if ( $status[$i] eq $service->{"likeEndpoint"}{"status"} ) {
|
|
$status_index = $i;
|
|
last;
|
|
}
|
|
}
|
|
|
|
return $status[ $status_index - 1 ];
|
|
}
|
|
|
|
our @EXPORT = qw(
|
|
parse_song_artists
|
|
parse_song_artists_runs
|
|
parse_song_runs
|
|
parse_song_album
|
|
parse_song_library_status
|
|
parse_song_menu_tokens
|
|
parse_like_status
|
|
);
|
|
|
|
1;
|