diff --git a/README b/README index 676cf1f..86a15c5 100644 --- a/README +++ b/README @@ -2,3 +2,5 @@ unweb er min samling af forskellige scripts til at undgå at bruge webapps. tv2east.dk/getlive.sh Udtræk HLS playlist til Livestream af TV2Øst. +youtube.com/search.pl + Søg på youtube. diff --git a/youtube.com/README b/youtube.com/README new file mode 100644 index 0000000..de181c2 --- /dev/null +++ b/youtube.com/README @@ -0,0 +1,21 @@ +Search youtube. + +The script can only show the first page of results. + +Usage: ./search.pl [--sort][--uploaded][--type][--duration][--features] QUERY + +Options: +--sort upload|views|rating|relevance +--uploaded hour|day|week|month|year +--type video|channel|playlist|movie +--duration short|long +--features live|4k|hd|sub|cc|360|vr180|3d|hdr|location|purchased + +Dependencies: + use Encode; + use JSON::XS; + use HTML::TreeBuilder 5 -weak; + use LWP; + use LWP::UserAgent; + use MIME::Base64; + use Getopt::Long; diff --git a/youtube.com/search.pl b/youtube.com/search.pl new file mode 100755 index 0000000..553c83d --- /dev/null +++ b/youtube.com/search.pl @@ -0,0 +1,243 @@ +#!/usr/bin/perl +# Copyright (c) 2021 smpl +# SPDX-Identifier: Zlib + +use Encode; +use JSON::XS; +use HTML::TreeBuilder 5 -weak; +use LWP; +use LWP::UserAgent; +use MIME::Base64; +use Getopt::Long; +use strict; + +sub print_help { + print "Usage: " . $0 . " [--sort][--uploaded][--type][--duration][--features] QUERY\n" . + "--sort upload|views|rating|relevance\n" . + "--uploaded hour|day|week|month|year\n" . + "--type video|channel|playlist|movie\n" . + "--duration short|long\n" . + "--features live|4k|hd|sub|cc|360|vr180|3d|hdr|location|purchased\n\n"; +} + +sub parse_cli { + my %options = ( + "sort" => { + "upload" => "\x08\x02", + "views" => "\x08\x03", + "rating" => "\x08\x01", + "relevance" => "\x08\x00" + }, + "uploaded" => { + "hour" => "\x08\x01", + "day" => "\x08\x02", + "week" => "\x08\x03", + "month" => "\x08\x04", + "year" => "\x08\x05" + }, + "type" => { + "video" => "\x10\x01", + "channel" => "\x10\x02", + "playlist" => "\x10\x03", + "movie" => "\x10\x04" + }, + "duration" => { + "short" => "\x18\x01", + "long" => "\x18\x02" + }, + "features" => { + "live" => "\x40\x01", + "4k" => "\x70\x01", + "hd" => "\x20\x01", + "sub" => "\x28\x01", + "cc" => "\x30\x01", + "360" => "\x78\x01", + "vr180" => "\xd0\x01\x01", + "3d" => "\x38\x01", + "hdr" => "\xc8\x01\x01", + "location" => "\xb8\x01\x01", + "purchased" => "\x48\x01" + } + ); + + my ($sp, $sp_raw); + my ($sort, $uploaded, $type, $duration, $features); + my $filter_bytes = 0; + my $filter = ""; + + GetOptions( + "sort:s" => \$sort, + "uploaded:s" => \$uploaded, + "type:s" => \$type, + "duration:s" => \$duration, + "features:s" => \$features + ) or die("Error in command line arguments\n"); + + if($sort && defined($options{"sort"}{$sort})) { + $sp_raw = $options{"sort"}{$sort}; + } + + if($type ne "video" && $type ne "movie") { + if($duration && defined($options{"duration"}{$duration})) { + print "\n--duration can only be combined with type video or movie.\n\n"; + print_help; + exit 1; + } + if($features && defined($options{"features"}{$features})) { + print "\n--features can only be combined with type video or movie.\n\n"; + print_help; + exit 1; + } + if($uploaded && defined($options{"uploaded"}{$uploaded})) { + print "\n--uploaded can only be combined with type video or movie.\n\n"; + print_help; + exit 1; + } + } + + if($uploaded && defined($options{"uploaded"}{$uploaded})) { + $filter_bytes += bytes::length($options{"uploaded"}{$uploaded}); + $filter .= $options{"uploaded"}{$uploaded}; + } + if($type && defined($options{"type"}{$type})) { + $filter_bytes += bytes::length($options{"type"}{$type}); + $filter .= $options{"type"}{$type}; + } + if($duration && defined($options{"duration"}{$duration})) { + $filter_bytes += bytes::length($options{"duration"}{$duration}); + $filter .= $options{"duration"}{$duration}; + } + if($features && defined($options{"features"}{$features})) { + $filter_bytes += bytes::length($options{"features"}{$features}); + $filter .= $options{"features"}{$features}; + } + + if($filter_bytes > 0) { + $sp = encode_base64($sp_raw . "\x12" . chr($filter_bytes) . $filter); + } else { + $sp = encode_base64($sp_raw); + } + + $sp =~ s/([+\/=])/sprintf("%%25%02X", ord($1))/eg; + + return $sp; +} + +my $sp = ""; + +if($#ARGV != 0) { + if($#ARGV < 0) { + print_help; + exit; + } + + $sp = parse_cli; + + if($#ARGV != 0) { + print_help; + exit; + } +} + +my $params = $ARGV[0]; +if(bytes::length($sp) > 0) { + $params .= "&sp=".$sp; +} + +my $ua = LWP::UserAgent->new; +$ua->agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0"); +$ua->cookie_jar( {} ); +$ua->cookie_jar()->set_cookie(0, 'CONSENT', 'YES+cb.20210530-19-p0.en+FX+027', '/', '.youtube.com'); +my $response = $ua->get('https://www.youtube.com/results?search_query='.$params); + +my $jsonobject; +my $coder = JSON::XS->new->pretty->allow_nonref; +my $tree = HTML::TreeBuilder->new; + +$tree->parse_content($response->content); + +my $body = $tree->find_by_tag_name('body'); + +my $ytInitialData_tag = $body->look_down( + _tag => "script", + sub { $_[0]->as_HTML =~ /ytInitialData/ } +); + +$ytInitialData_tag->as_HTML =~ /.*ytInitialData[^=]*= (.*});.*/; +my $ytInitialData = $1; + +$jsonobject = $coder->decode($ytInitialData); +my $utf8_encoded_json_text = $coder->encode($jsonobject); + +my @results = @{$jsonobject->{"contents"}->{'twoColumnSearchResultsRenderer'}->{'primaryContents'}->{'sectionListRenderer'}->{'contents'}->[0]->{'itemSectionRenderer'}->{'contents'}}; + +sub get_textarray +{ + my $aref = shift @_; + my $text = ""; + foreach my $line (@{$aref}) { + $text .= $line->{'text'}; + } + return $text; +} + +sub print_video +{ + my $video = shift @_; + print "Title: " . $video->{'videoRenderer'}->{'title'}->{'runs'}->[0]->{'text'} . "\n"; + print "Length: " . $video->{'videoRenderer'}->{'lengthText'}->{'simpleText'} . "\n"; + print "Published: " . $video->{'videoRenderer'}->{'publishedTimeText'}->{'simpleText'} . "\n"; + print "Views: " . $video->{'videoRenderer'}->{'viewCountText'}->{'simpleText'} . "\n"; + print "By: " . $video->{'videoRenderer'}->{'ownerText'}->{'runs'}->[0]->{'text'} . "\n"; + print "Feed: "; + my $feeduri = $video->{'videoRenderer'}->{'ownerText'}->{'runs'}->[0]->{'navigationEndpoint'}->{'commandMetadata'}->{'webCommandMetadata'}->{'url'}; + if($feeduri =~ m{^/user/}) { + print 'https://www.youtube.com/feeds/videos.xml?user=' . $feeduri =~ s!^/user/!!r; + } else { + print 'http://www.youtube.com/feeds/videos.xml?channel_id=' . $feeduri =~ s!^/channel/!!r; + } + print "\n"; + print "Url: https://www.youtube.com/watch?v=" . $video->{'videoRenderer'}->{'videoId'} . "\n\n"; +} + +sub print_playlist +{ + my $playlist = shift @_; + print "Title: " . $playlist->{'playlistRenderer'}->{'title'}->{'simpleText'} . "\n"; + print "Videos: " . $playlist->{'playlistRenderer'}->{'videoCount'} . "\n"; + print "Url: https://www.youtube.com/playlist?list=" . $playlist->{'playlistRenderer'}->{'playlistId'} . "\n"; + print "Feed: https://www.youtube.com/feeds/videos.xml?playlist_id=" . $playlist->{'playlistRenderer'}->{'playlistId'} . "\n\n"; +} + +sub print_channel +{ + my $channel = shift @_; + print "Title: " . $channel->{'channelRenderer'}->{'title'}->{'simpleText'} . "\n"; + if($channel->{'channelRenderer'}->{'descriptionSnippet'}) { + print "Description: " . get_textarray($channel->{'channelRenderer'}->{'descriptionSnippet'}->{'runs'}) . "\n"; + } + if($channel->{'channelRenderer'}->{'subscriberCountText'}) { + print "Subscribers: " . $channel->{'channelRenderer'}->{'subscriberCountText'}->{'simpleText'} . "\n"; + } + print "Videos: " . get_textarray($channel->{'channelRenderer'}->{'videoCountText'}->{'runs'}) . "\n"; + print "Url: https://www.youtube.com/channel/" . $channel->{'channelRenderer'}->{'channelId'} . "\n"; + print "Feed: http://www.youtube.com/feeds/videos.xml?channel_id=" . $channel->{'channelRenderer'}->{'channelId'} . "\n\n"; +} + +foreach my $item (@results) { + my ($key, $title, $length, $date, $views, $id); + foreach my $k (keys %{$item}) { + $key = $k; + } + + if($key eq 'videoRenderer') { + print "[video]\n"; + print_video($item); + } elsif($key eq 'playlistRenderer') { + print "[playlist]\n"; + print_playlist($item); + } elsif($key eq 'channelRenderer') { + print "[channel]\n"; + print_channel($item); + } +}