ytbrowser-tui/ytbrowser.pl

235 lines
8.0 KiB
Perl
Raw Normal View History

2021-02-27 01:05:38 -07:00
#!/usr/bin/perl
use strict;
use warnings;
use JSON;
use Time::HiRes qw( gettimeofday );
use Digest::SHA1 qw( sha1_hex );
use Curses;
use Curses::UI;
#use Data::Dumper;
#$Data::Dumper::Indent = 1;
#$Data::Dumper::Maxdepth = 1;
my $homeDir = "/home/bluesaxman/";
my $cookiesFile = $homeDir.".scripts/cookies.txt";
my $cachedFile = $homeDir.".yt_cache";
my $playlistFile = $homeDir.".yt_play";
my $debugFile = $homeDir.".yt_last";
2021-02-27 01:05:38 -07:00
my @watchlist = ();
my @suggestArray = ();
2021-03-01 15:59:23 -07:00
my @watchArray = ();
2021-02-27 01:05:38 -07:00
my $continuation;
my $visitorID;
my $pageID;
my $ytclient;
my $ytclientVersion;
my $apiKey;
my $sapisidhash;
my $json;
my $jsonDirty;
my $rawFile;
my @videos = ();
my $recursionWatchdog = 0;
my $cookieCrisp = "";
open (COOKIE, $cookiesFile) or die "I CANNOT FUNCTION WITHOUT COOKIES!!!\n";
while (<COOKIE>) { $cookieCrisp .= $_ }
close (COOKIE);
$cookieCrisp =~ m/\.youtube\.com[^\n]*SAPISID\s([^\n]*)/;
#sha1(new Date().getTime() + " " + SAPISID + " " + origin)
$sapisidhash = (gettimeofday())[0]."_".sha1_hex((gettimeofday())[0]." ".$1." https://www.youtube.com");
open(WATCH, $playlistFile) or goto SKIPWATCHLOAD;
2021-03-01 15:59:23 -07:00
my $readFile = "";
while(<WATCH>) { $readFile .= $_; }
2021-02-27 01:05:38 -07:00
close(WATCH);
2021-03-01 15:59:23 -07:00
@watchlist = @{decode_json($readFile)};
2021-02-27 01:05:38 -07:00
SKIPWATCHLOAD:
2021-03-01 15:59:23 -07:00
my %tests = map { $_->{id} => 1 } @watchlist;
2021-02-27 01:05:38 -07:00
# Currently we read from a file that was rerived from
# curl -b ~/.scripts/cookies.txt https://www.youtube.com
# So we don't make Youtube mad at us. eventually we will
# replace the open with this.
sub addVideos {
my @contents = shift;
my $myProgress = shift;
2021-02-27 01:05:38 -07:00
for (@{$contents[0]}) {
if (exists($_->{'richItemRenderer'})) {
if ($_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'thumbnailOverlays'}[0]{'thumbnailOverlayResumePlaybackRenderer'}) { next }
my $idToken = $_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'videoId'};
unless ($idToken) { next }
if (exists($tests{$idToken})) { next }
push @videos, { id => $_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'videoId'},
title => $_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'title'}{'runs'}[0]->{'text'},
published => $_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'publishedTimeText'}{'simpleText'},
runtime => $_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'lengthText'}{'simpleText'},
channel => $_->{'richItemRenderer'}{'content'}{'videoRenderer'}{'ownerText'}{'runs'}[0]->{'text'}
};
} elsif (exists($_->{'continuationItemRenderer'})) {
$recursionWatchdog++;
$myProgress->pos($recursionWatchdog);
$myProgress->draw();
unless($recursionWatchdog > 100) { sleep 2; return additionalGet($_->{'continuationItemRenderer'},$myProgress); }
2021-02-27 01:05:38 -07:00
}
}
}
sub firstGet {
my $myProgress = shift;
2021-02-27 01:05:38 -07:00
$rawFile = `curl -s -b $cookiesFile https://www.youtube.com`;
$myProgress->draw();
2021-02-27 01:05:38 -07:00
$rawFile =~ m/ytInitialData =([^<]*)/;
$jsonDirty = $1;
chop $jsonDirty;
$json = decode_json $jsonDirty;
$visitorID = $json->{'responseContext'}{'webResponseContextExtensionData'}{'ytConfigData'}{'visitorData'};
$visitorID =~ s/%3D/=/g;
addVideos($json->{'contents'}{'twoColumnBrowseResultsRenderer'}{'tabs'}[0]->{'tabRenderer'}{'content'}{'richGridRenderer'}{'contents'},$myProgress);
2021-02-27 01:05:38 -07:00
}
sub additionalGet {
# We need these tokens in order to get more suggestions.
2021-02-27 01:05:38 -07:00
my $continuationItem = shift;
my $myProgress = shift;
2021-02-27 01:05:38 -07:00
$continuation = $continuationItem->{'continuationEndpoint'}{'continuationCommand'}{'token'};
$pageID = (split(/\|/, $json->{'responseContext'}{'mainAppWebResponseContext'}{'datasyncId'}))[0];
$ytclient = $json->{'responseContext'}{'serviceTrackingParams'}[2]->{'params'}[1]->{'value'};
$ytclientVersion = $json->{'responseContext'}{'serviceTrackingParams'}[2]->{'params'}[2]->{'value'};
# hopefully I can find this in the json
# for now this will have to do
$rawFile =~ m/INNERTUBE_API_KEY":"([^"]*)/;
$apiKey = $1;
my $requestJson = encode_json({
context => {
client => {
hl => "en",
gl => "US",
visitorData => $visitorID,
clientName => $ytclient,
clientVersion => $ytclientVersion
},
user => { lockedSafetyMode => 'false' },
request => { useSsl => 'true' }
},
continuation => $continuation
});
# Not the curl the world wants, but the curl the world needs
$rawFile = `curl -s -X POST -H 'content-type: application/json' -H 'authorization: SAPISIDHASH $sapisidhash' -H 'referer: https://www.youtube.com/' -H 'x-goog-authuser: 0' -H 'x-goog-pageid: $pageID' -H 'x-goog-visitor-id: $visitorID' -H 'x-origin: https://www.youtube.com' -H 'x-youtube-client-name: 1' -H 'x-youtube-client-version: $ytclientVersion' -b $cookiesFile -d '$requestJson' https://www.youtube.com/youtubei/v1/browse?key=$apiKey`;
$json = decode_json $rawFile;
if (exists($json->{'onResponseReceivedActions'})) {
return addVideos($json->{'onResponseReceivedActions'}[0]->{'appendContinuationItemsAction'}{'continuationItems'},$myProgress);
2021-02-27 01:05:38 -07:00
}
}
sub loadSugs {
my $counter = 1;
for (@videos) {
my $title = $_->{title};
my $playtime = $_->{runtime};
unless($playtime) { $playtime = "Live"; }
2021-02-27 01:05:38 -07:00
push(@suggestArray, sprintf('%3.3s.',$counter).sprintf('%8.8s ',$playtime).$title);
$counter ++;
}
2021-03-01 15:59:23 -07:00
for (@watchlist) {
my $title = $_->{title};
my $playtime = $_->{runtime};
unless($playtime) { $playtime = "Live"; }
push(@watchArray, sprintf('%8.8s ',$playtime).$title);
}
2021-02-27 01:05:38 -07:00
}
open(CACHED, $cachedFile) or goto SKIPCACHED;
2021-03-01 15:59:23 -07:00
$readFile = "";
2021-02-27 01:05:38 -07:00
while (<CACHED>) { $readFile .= $_; }
close(CACHED);
@videos = @{decode_json($readFile)};
SKIPCACHED:
`touch $debugFile`;
`touch $cachedFile`;
`touch $playlistFile`;
open(DEBUG, ">", $debugFile);
open(CACHING, ">", $cachedFile);
open(SAVE, ">", $playlistFile);
sub sync2files {
truncate(DEBUG,0);
truncate(CACHING,0);
truncate(SAVE,0);
seek(DEBUG,0,0);
seek(CACHING,0,0);
seek(SAVE,0,0);
if ($rawFile) { print DEBUG $rawFile; }
print CACHING encode_json(\@videos);
2021-03-01 15:59:23 -07:00
print SAVE encode_json(\@watchlist);
}
2021-02-27 01:05:38 -07:00
####################################################
# Now that we have our data from Youtube, lets make
# it easy to look at and select.
my $cui = new Curses::UI( -clear_on_exit => 1, -color_support => 1 );
my $win = $cui->add( 'main', 'Window');
my $suggestions = $win->add( 'suggestions', 'Listbox', -pad => 1, -ipad => 1, -border => 1, -title => "Suggestions", -fg => "blue", -bg => "white", -height => $win->height() / 2 , -values => \@suggestArray);
2021-03-01 15:59:23 -07:00
my $wlist = $win->add( 'watchlist', 'Listbox', -pad => 1, -ipad => 1, -border => 1, -title => "Watch list", -fg => "green", -bg => "black", -height => $win->height() / 2, -y => $suggestions->height(), -values => \@watchArray);
2021-02-27 01:05:38 -07:00
loadSugs();
$cui->set_binding( sub { $cui->mainloopExit(); }, "q" );
$suggestions->set_binding( sub {
2021-03-01 15:59:23 -07:00
push(@watchArray, (split(". " splice(@suggestArray,$suggestions->get_active_id(),1),2))[1] );
push(@watchlist, splice(@videos,$suggestions->get_active_id(),1));
sync2files();
2021-02-27 01:05:38 -07:00
$suggestions->draw();
$wlist->draw();
}, KEY_ENTER);
2021-03-01 15:59:23 -07:00
$suggestions->set_binding( sub {
2021-02-27 01:05:38 -07:00
my $video = shift(@watchlist);
2021-03-01 15:59:23 -07:00
$cui->status("Playing Video, controls will resume once video is compelte\n Currently Playing: ".shift(@watchArray));
`mpv --ytdl-raw-options=cookies=$cookiesFile,mark-watched= --terminal=no https://www.youtube.com/watch?v=$video->{id}`;
sync2files();
2021-02-27 01:05:38 -07:00
$win->draw();
$suggestions->draw();
$wlist->draw();
}, "p");
2021-03-01 15:59:23 -07:00
$suggestions->set_binding( sub {
2021-02-27 01:05:38 -07:00
splice(@videos,0,scalar(@videos));
splice(@suggestArray,0,scalar(@suggestArray));
$cui->status("Refreshing from Youtube, please wait...");
$recursionWatchdog = 0;
my $progressBar = $win->add('refresh', 'Progressbar', -max => 50, -pos => 0, -y => $suggestions->height() / 2 );
$progressBar->focus();
firstGet($progressBar);
2021-02-27 01:05:38 -07:00
loadSugs();
sync2files();
2021-02-27 01:05:38 -07:00
$win->draw();
$suggestions->draw();
$wlist->draw();
}, "r");
2021-03-01 15:59:23 -07:00
$suggestions->set_binding( sub {
2021-02-27 01:05:38 -07:00
my $vid = $videos[$suggestions->get_active_id()];
$cui->status("Video: ".$vid->{title}."\nChannel: ".$vid->{channel}."\nRuntime: ".$vid->{runtime}."\nPublished: ".$vid->{published}."\nURL: https://www.youtube.com/watch?v=".$vid->{id});
}, "i");
$suggestions->focus();
$cui->mainloop();
#Save leftovers
sync2files();
2021-02-27 01:05:38 -07:00
close(DEBUG);
close(CACHING);
close(SAVE);