#!/usr/bin/env perl
use strict;
use warnings;
use Sys::Hostname;
use Scalar::Util qw(looks_like_number);
use File::HomeDir qw(home);
use File::Basename qw(basename fileparse);
use Cwd qw(cwd);
use Env qw(BROWSER TERMINAL);
use Term::ReadKey;
use Capture::Tiny qw(capture);
use POSIX qw(setsid);
use Data::UUID;
use feature qw(switch);
no warnings qw(experimental::smartmatch);
my $menu = "fzf";
use constant {
TERMINAL_PATH => $TERMINAL,
MPV_PATH => 'mpv',
YTDL_PATH => 'yt-dlp',
IMGVIEW_PATH => 'nsxiv -b -a',
FETCH_PATH => 'fetch',
CURL_PATH => 'curl',
ZATHURA_PATH => 'zathura',
W3M_PATH => 'w3m',
BROWSER_PATH => $BROWSER,
NOTIFYSEND_PATH => 'notify-send',
COPYQ_PATH => 'copyq',
HOSTNAME => (split /\./, hostname())[0]
};
my %func_list = (
'1 vid' => \&vid,
'2 img' => \&img_pdf,
'3 dl' => \&dl,
'4 audio' => \&audio,
'5 pdf' => \&img_pdf,
'6 w3m' => \&w3m,
'7 browser' => \&browser,
'8 clip' => \&clip,
);
use constant QUAL_LIST => "" .
"uncap" . "\n" .
"1440" . "\n" .
"1080" . "\n" .
"720" . "\n" .
"480" . "\n" .
"360" . "\n" .
"240" . "\n" .
"144" . "\n";
sub vid
{
my ($url, $article_name) = @_;
my $pid;
$article_name = "some media" if ($article_name eq "");
$pid = fork();
if (not $pid) {
setsid();
close_io();
system(
NOTIFYSEND_PATH,
'-t', '2000',
'playing media',
' playing ' . $article_name . ''
);
if (system(TERMINAL_PATH, "-e", MPV_PATH, $url) != 0) {
exec(
NOTIFYSEND_PATH,
'-u', 'critical',
'-t', '10000',
'playback failed',
' failed to open ' . $url . ''
);
}
return;
}
return;
}
sub img_pdf
{
my ($url, $article_name, $key) = @_;
my $file_name;
my $new_file_name;
my $pid;
my $ret;
my $tmp;
my $ug;
my $uuid;
$article_name = "some media" if ($article_name eq "");
$pid = fork();
if (not $pid) {
setsid();
close_io();
chdir '/tmp' or die $!;
$file_name = $url;
$file_name =~ s/.+\///g;
if (is_yt($url) != 0) {
system(
NOTIFYSEND_PATH,
'-t', '5000',
'fetching thumbnail',
' fetching thumbnail for ' . $article_name . ''
);
($tmp, undef, $ret) = capture {
system(YTDL_PATH, '--get-thumbnail', $url);
};
chomp $tmp;
if ($ret != 0) {
system(
NOTIFYSEND_PATH,
'-u', 'critical',
'failed to get thumbnail',
' failed to get thumbail for ' . $article_name . ''
);
return;
}
$url = $tmp;
}
else {
system(
NOTIFYSEND_PATH,
'-t', '5000',
'download started',
' fetching '. $file_name . ' to /tmp'
);
}
$file_name = $url;
$file_name =~ s/.+\///g;
given (HOSTNAME) {
when(["mother", "po-rbo"]) {
system(CURL_PATH, "-fsSLO", $url);
}
when("mars") {
system(FETCH_PATH, '-q', $url);
}
}
my ($name, undef, $ext) = fileparse($file_name, qr/\.[^.]*/);
$ug = Data::UUID->new;
$uuid = $ug->create_str();
$ext =~ s/\?.*//;
$new_file_name = $uuid . $ext;
rename($file_name, $new_file_name) or die $!;
if ($key eq "2 img") {
$ret = system(IMGVIEW_PATH . " " . $new_file_name);
}
else {
$ret = system(ZATHURA_PATH, $new_file_name);
}
if ($ret != 0) {
system(
NOTIFYSEND_PATH,
'-u', 'critical',
'failed to open image',
' failed to open file /tmp/'. $new_file_name . ''
);
}
unlink($new_file_name);
return;
}
return;
}
sub dl
{
my ($url) = @_;
my $count;
my $file_name;
my $i;
my $list;
my $pid;
my $pid2;
my $pwd;
my $quality;
my $ret;
my $tmpfile;
my $val;
my @wc;
if (is_yt($url) != 0) {
$list = QUAL_LIST;
$quality = `printf "$list" | $menu --prompt 'quality > '`;
if (not $quality) {
return;
}
chomp $quality;
}
$pid = fork();
if (not $pid) {
setsid();
close_io();
$ret = -1;
chdir home() . "/dl" or
chdir home() . "/Downloads" or
chdir home() or die $!;
if (basename(cwd()) eq basename(home())) {
$pwd = '~/';
}
else {
$pwd = '~/' . basename(cwd());
}
if (is_yt($url) != 0) {
$file_name = get_yt_vid_name($url);
}
else {
$file_name = $url;
$file_name =~ s/.+\///g;
}
chomp $file_name;
@wc = split / /, $file_name;
if (@wc > 8) {
$file_name = "";
$i = 0;
while ($i < 8) {
$file_name .= "$wc[$i] ";
$i++;
}
$file_name .= "[...]";
}
$file_name =~ s/^[0-9]+/\[\.\.\.\]/;
system(
NOTIFYSEND_PATH,
'-t', '2000',
'download starting',
' downloading ' . $file_name . ''
);
if (is_yt($url) != 0) {
$tmpfile = `mktemp`;
chomp $tmpfile;
$pid2 = fork();
if (not $pid2) {
(undef, undef, $ret) = capture {
if ($quality eq 'uncap') {
system(YTDL_PATH . ' --newline --add-metadata ' . $url . ' >' . $tmpfile);
}
elsif ($quality eq '1440') {
system(YTDL_PATH . " -f '308+140' --newline --add-metadata " . $url . ' >' . $tmpfile);
}
elsif ($quality eq '1080') {
system(YTDL_PATH . " -f '299+140' --newline --add-metadata " . $url . ' >' . $tmpfile);
}
elsif ($quality eq '720') {
system(YTDL_PATH . " -f '298+140' --newline --add-metadata " . $url . ' >' . $tmpfile);
}
else {
system(YTDL_PATH . " -f '[height<=" . $quality . "]' --newline --add-metadata " . $url . ' >' . $tmpfile);
}
};
if ($ret == 0) {
system(
NOTIFYSEND_PATH,
'-u', 'normal',
'download complete',
' ' . $file_name . ' downloaded successfully to ' .
'' . $pwd . ''
);
}
else {
system(
NOTIFYSEND_PATH,
'-u', 'critical',
'download failed',
' failed to download ' . $file_name . ''
);
}
return;
}
elsif ($pid2 < 0) {
system(
NOTIFYSEND_PATH,
'-u', 'critical',
'download failed',
' failed to fork(2)'
);
return;
}
else {
$count = 0;
while (1) {
$val = `tail -n 1 "$tmpfile" | awk '{print \$2}' | tr -d '%'`;
if (looks_like_number($val) != 0) {
system(
NOTIFYSEND_PATH,
'-h',
'int:value:' . $val,
'-u', 'low',
'-t', '5000',
'downloading',
' downloading ' . $file_name . ''
);
if ($val == 100 || system('pgrep yt-dlp >/dev/null 2>&1') != 0) {
last;
}
}
else {
if ($count == 10) {
last;
}
$count += 1;
}
sleep 2;
}
unlink $tmpfile;
}
return;
}
else {
(undef, undef, $ret) = capture {
if (HOSTNAME eq "mother" or "po-rbo") {
system(CURL_PATH, "-fsSLO", $url);
} elsif (HOSTNAME == "mars") {
system(FETCH_PATH, $url);
}
};
if ($ret == 0) {
system(
NOTIFYSEND_PATH,
'-u', 'normal',
'download complete',
' ' . $file_name . ' downloaded successfully to ' .
'' . $pwd . ''
);
}
else {
system(
NOTIFYSEND_PATH,
'-u', 'critical',
'download failed',
' failed to download ' . $file_name . ''
);
}
}
return;
}
elsif ($pid < 0) {
system(
NOTIFYSEND_PATH,
'-u', 'critical',
'-t', '10000',
'download failed',
' failed to fork(2)'
);
return;
}
return;
}
sub audio
{
my ($url, $article_name) = @_;
my $pid;
$pid = fork();
if (not $pid) {
setsid();
close_io();
system(
NOTIFYSEND_PATH,
'-t', '2000',
'playing media',
' playing ' . $article_name . ''
);
if (system(
TERMINAL_PATH,
"-e",
MPV_PATH,
"--vo=null",
"--video=no",
"--no-video",
$url
) != 0) {
exec(
NOTIFYSEND_PATH,
'-u', 'critical',
'-t', '10000',
'playback failed',
' failed to open ' . $url . ''
);
}
return;
}
return;
}
sub w3m
{
my ($url) = @_;
my $pid;
$pid = fork();
if (not $pid) {
setsid();
close_io();
if (system(TERMINAL_PATH, '-e', W3M_PATH, $url) != 0) {
exec(
NOTIFYSEND_PATH,
'-u', 'critical',
'-t', '10000',
'w3m failed',
' failed to open ' . $url . ''
);
}
return;
}
return;
}
sub browser
{
my ($url) = @_;
my $pid;
$pid = fork();
if (not $pid) {
setsid();
close_io();
exec(BROWSER_PATH, $url);
}
return;
}
sub clip
{
my ($url) = @_;
my $pid;
$pid = fork();
if (not $pid) {
setsid();
close_io();
if (defined $ENV{WAYLAND_DISPLAY}) {
system("echo -n " . $url . " | wl-copy");
return;
}
else {
exec(COPYQ_PATH, "copy", $url);
}
}
system(
NOTIFYSEND_PATH,
'-t', '2000',
'clipped url',
' clipped url'
);
return;
}
sub close_io
{
open STDIN, '<', '/dev/null' or die $!;
open STDOUT, '>', '/dev/null' or die $!;
open STDERR, '>', '/dev/null' or die $!;
return;
}
sub is_yt
{
my ($url) = @_;
return $url =~ m{
^(?:https?://)?
(?:www\.|m\.)?
(?:youtube\.com|youtu\.be)/
(?:
(?:watch\?v=|embed/|v/|shorts/)?
)
([\w-]{11})
}x;
}
sub get_yt_vid_name
{
my ($url) = @_;
my $file_name;
system(
NOTIFYSEND_PATH,
'-t', '4000',
'checking name',
' looking for video name'
);
$file_name = `yt-dlp -e $url`;
if (not $file_name) {
$file_name = "yt video";
}
chomp $file_name;
return $file_name;
}
sub fzf_prompt
{
my ($url) = @_;
my $answer;
my $list = join("\n", sort keys %func_list);
$answer = `printf "$list\n" | $menu --prompt '$url > '`;
chomp $answer;
return $answer;
}
sub linkview
{
my $answer;
if (@ARGV == 0 || $ARGV[0] eq '') {
print STDERR "linkview: no URL\n";
exit 1;
}
$answer = fzf_prompt($ARGV[0]);
return unless exists $func_list{$answer};
$func_list{$answer}->($ARGV[0], $ARGV[1], $answer);
return;
}
linkview();
__END__