Initial Commit
This commit is contained in:
commit
192ffdb527
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
||||
### ytbrowser
|
||||
This script lets you browse your suggestions on youtube without having to load all the overhead. It also lets you watch videos with mpv from its TUI.
|
||||
|
||||
- Requirents:
|
||||
- Perl Moduels
|
||||
- Time::HiRes
|
||||
- Digest::SHA1
|
||||
- Curses
|
||||
- Curses::UI
|
||||
- A cookies text file (with your current google/youtube seesion data)
|
||||
- yt-download
|
||||
- mpv
|
214
ytbrowser.pl
Executable file
214
ytbrowser.pl
Executable file
@ -0,0 +1,214 @@
|
||||
#!/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 $cookiesFile = "/home/bluesaxman/.scripts/cookies.txt";
|
||||
|
||||
my @watchlist = ();
|
||||
my @suggestArray = ();
|
||||
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, "./watchlist.txt") or goto SKIPWATCHLOAD;
|
||||
while(<WATCH>) {
|
||||
my $item = $_;
|
||||
chomp $item;
|
||||
push(@watchlist, $item);
|
||||
}
|
||||
close(WATCH);
|
||||
SKIPWATCHLOAD:
|
||||
my %tests = map { (split("=",$_))[1] => 1 } @watchlist;
|
||||
|
||||
# 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;
|
||||
|
||||
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++;
|
||||
unless($recursionWatchdog > 100) { sleep 2; return additionalGet($_->{'continuationItemRenderer'}); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub firstGet {
|
||||
# open(FH, "./ytsample");
|
||||
# $rawFile = "";
|
||||
# while (<FH>) { $rawFile .= $_ }
|
||||
# close(FH);
|
||||
|
||||
$rawFile = `curl -s -b $cookiesFile https://www.youtube.com`;
|
||||
|
||||
$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'});
|
||||
}
|
||||
|
||||
sub additionalGet {
|
||||
# We need this token in order to get more suggestions.
|
||||
my $continuationItem = shift;
|
||||
$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
|
||||
|
||||
# Our test sample data for a continuation
|
||||
# open(FH2, "./ytsample_continuation");
|
||||
# $rawFile = "";
|
||||
# while (<FH2>) { $rawFile .= $_; }
|
||||
# close(FH2);
|
||||
|
||||
$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'});
|
||||
}
|
||||
}
|
||||
|
||||
sub loadSugs {
|
||||
my $counter = 1;
|
||||
for (@videos) {
|
||||
my $title = $_->{title};
|
||||
my $playtime = $_->{runtime};
|
||||
push(@suggestArray, sprintf('%3.3s.',$counter).sprintf('%8.8s ',$playtime).$title);
|
||||
$counter ++;
|
||||
}
|
||||
}
|
||||
|
||||
open(CACHED, "./cachedSug") or goto SKIPCACHED;
|
||||
my $readFile = "";
|
||||
while (<CACHED>) { $readFile .= $_; }
|
||||
close(CACHED);
|
||||
@videos = @{decode_json($readFile)};
|
||||
SKIPCACHED:
|
||||
|
||||
####################################################
|
||||
# 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);
|
||||
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 => \@watchlist);
|
||||
loadSugs();
|
||||
|
||||
$cui->set_binding( sub { $cui->mainloopExit(); }, "q" );
|
||||
$suggestions->set_binding( sub {
|
||||
my $vid = splice(@videos,$suggestions->get_active_id(),1);
|
||||
splice(@suggestArray,$suggestions->get_active_id(),1);
|
||||
push(@watchlist, "https://www.youtube.com/watch?v=".$vid->{id});
|
||||
# $cui->status("https://www.youtube.com/watch?v=".$vid->{id}."\nhas been added to the watchlist. Watchlist is ".(scalar(@watchlist) + 1)." long.");
|
||||
$suggestions->draw();
|
||||
$wlist->draw();
|
||||
}, KEY_ENTER);
|
||||
$cui->set_binding( sub {
|
||||
my $video = shift(@watchlist);
|
||||
$cui->status("Playing Video, controls will resume once video is compelte");
|
||||
`mpv --ytdl-raw-options=cookies=$cookiesFile,mark-watched= --terminal=no $video`;
|
||||
$win->draw();
|
||||
$suggestions->draw();
|
||||
$wlist->draw();
|
||||
}, "p");
|
||||
$cui->set_binding( sub {
|
||||
splice(@videos,0,scalar(@videos));
|
||||
splice(@suggestArray,0,scalar(@suggestArray));
|
||||
$cui->status("Refreshing from Youtube, please wait...");
|
||||
firstGet();
|
||||
loadSugs();
|
||||
$win->draw();
|
||||
$suggestions->draw();
|
||||
$wlist->draw();
|
||||
|
||||
}, "r");
|
||||
$cui->set_binding( sub {
|
||||
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
|
||||
`touch ./lastPull`;
|
||||
open(DEBUG, ">", "./lastPull");
|
||||
print DEBUG $rawFile;
|
||||
close(DEBUG);
|
||||
`touch ./cachedSug`;
|
||||
open(CACHING, ">", "./cachedSug");
|
||||
print CACHING encode_json(\@videos);
|
||||
close(CACHING);
|
||||
`touch ./watchlist.txt`;
|
||||
open(SAVE, ">", "./watchlist.txt");
|
||||
for (@watchlist) { print SAVE $_."\n"; }
|
||||
close(SAVE);
|
Loading…
x
Reference in New Issue
Block a user