dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

commit 9eb29d3cf773e01c86b9e0c3312a4405efc77c5f
parent 3976e2fd5ca5c1938031275f90f4b689fc38a7fb
Author: Alex Balgavy <alex@balgavy.eu>
Date:   Thu,  2 Jun 2022 16:17:02 +0200

linkhandler: refactor to dispatch tables

General code cleanup as well, plus some extra features/bugfixes.

Diffstat:
Mscripts/linkhandler | 398++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 261 insertions(+), 137 deletions(-)

diff --git a/scripts/linkhandler b/scripts/linkhandler @@ -1,17 +1,25 @@ #!/usr/bin/env perl +# vim: foldmethod=marker use strict; use warnings; +use constant EXIT_USER_CANCELLED => 130; + if ( @ARGV != 1 ) { die 'Link necessary.'; } -my ($link) = @ARGV; +my ($LINK) = @ARGV; my $CHOOSER = $ENV{'CHOOSER'}; +unless (length $CHOOSER) { + die 'Must set CHOOSER in environment.'; +} + my $HOME = $ENV{'HOME'}; my $TERM = $ENV{'TERM'}; -my $choice; -=pod +=begin +This subroutine takes a subroutine reference and executes the dereferenced subroutine in a fork. +Basically a way to handle things async by just writing `detach sub { .... }` in code. Using a fork is OK here because: "If a parent process terminates, then its "zombie" children (if any) are adopted by init(8), which automatically performs a wait to remove the zombies." @@ -26,180 +34,296 @@ sub detach { return; } -sub includes_strs { - my $substrs = shift; - my @included = grep { index( $link, $_ ) != -1 } @{$substrs}; - return scalar @included; +# Runs arguments in surrounding terminal, or creates a new one. +sub launch_in_terminal { + my $args = shift; + if (($TERM eq 'dumb') or (not defined $TERM)) { + system(qq(alacritty -e $args)); + } + else { + system($args); + } + return; +} + +# Send notification via my cross-platform-ish `notify` script +sub notify { + my ($title, $message) = @_; + system qq(notify '$title' '$message' linkhandler >/dev/null 2>&1); + return; +} + +# String checking functions {{{1 +# Checks if $str (includes|endswith|startswith) any of $substrs +sub checkstr { + my ($str, $mode, $substrs) = @_; + my @result; + if ($mode eq 'includes') { + @result = grep { index( $str, $_ ) != -1 } @{$substrs}; + } + elsif ($mode eq 'ends') { + @result = grep { $str =~ /\Q$_\E$/msx } @{$substrs}; + } + elsif ($mode eq 'starts') { + @result = grep { $str =~ /^\Q$_\E/msx } @{$substrs}; + } + return scalar @result; +} + +sub is_bandcamp { + my $link = shift; + return checkstr($link, 'includes', ['bandcamp.com', 'godisanastronaut.com']); +} + +sub is_video { + my $link = shift; + return (checkstr($link, 'ends', ['mkv', 'webm', 'mp4']) + or checkstr($link, 'includes', ['youtube.com/watch', 'youtube.com/playlist', 'yewtu.be', 'youtu.be', + 'hooktube.com', 'bitchute.com', 'videos.lukesmith.xyz', 'v.redd.it', 'fb.watch', 'vimeo.com'])); +} +sub is_image { + my $link = shift; + return checkstr($link, 'ends', ['png', 'jpg', 'jpe', 'jpeg', 'gif']); } -sub endswith_strs { - my $suffixes = shift; - my @endswith = grep { $link =~ /\Q$_\E$/msx } @{$suffixes}; - return scalar @endswith; +sub is_gifv { + my $link = shift; + return checkstr($link, 'ends', ['gifv']); } -sub startswith_strs { - my $prefixes = shift; - my @startswith = grep { $link =~ /^\Q$_\E/msx } @{$prefixes}; - return scalar @startswith; +sub is_audio { + my $link = shift; + return (checkstr($link, 'ends', ['mp3', 'flac', 'opus', 'mp3?source']) + or checkstr($link, 'includes', ['soundcloud.com'])); } +# Menu builders {{{1 +# Choose from @options (passed by reference) via $CHOOSER sub choose { my $options_ref = shift; my $options_str = join "\n", @{$options_ref}; my $selected = `printf '$options_str\n' | "$CHOOSER"`; $selected =~ s/\s+$//msx; + unless (length $selected) { + exit EXIT_USER_CANCELLED; # user interrupted + } return $selected; } -sub notify { - my ( $title, $message ) = @_; - system qq(notify '$title' '$message' linkhandler >/dev/null 2>&1); - return; +# Receives a dispatch table of strings to subroutines. +# Lets you select one of the strings via `choose`. +# Returns reference to corresponding subroutine. +sub menu { + my $tableref = shift; + + # Global system open -- should be available for every link + $tableref->{'Open (system)'} = sub { + my ($link) = @_; + system('open', $link); + notify 'Opening...', $link; + }; + + # Global copy -- should be available for every link + $tableref->{'Copy'} = sub { + my ($link) = @_; + system(qq(printf '%s' '$link' | clc)); + notify 'Copied to clipboard', $link; + }; + + # Choose an option + my @options = keys %{$tableref}; + my $choice = choose(\@options); + + # Return corresponding the subroutine reference + return $tableref->{$choice}; } -if ( includes_strs( ['bandcamp.com', 'godisanastronaut.com'] ) ) { - $choice = choose( [ 'Download', 'Play' ] ); - - if ( $choice eq 'Download' ) { - detach sub { - my $download_dir = "$HOME/Downloads/songs/listen to"; - system(qq(mkdir -p "$download_dir")); - chdir($download_dir); - ( my $name = $link ) =~ s!^.*/!!; - ( my $artist = $link ) =~ s|https*://||; - $artist =~ s/\.bandcamp\.com.*//; - - notify( "Downloading $name by $artist", "Downloading $link" ); - system(qq(mkdir -p $artist)) unless ( -d $artist ); - chdir($artist); - system(qq(mkdir -p $name)) unless ( -d $name ); - chdir($name); - system qq(youtube-dl -f mp3 -o "%(playlist_index)s %(title)s %(id)s.%(ext)s" '$link' >/dev/null 2>&1); - system(qq(printf "#EXTM3U\n#PLAYLIST:%s\n#EXTART:%s\n" "$name" "$artist" > "$name".m3u)); - system(qq(youtube-dl -f mp3 --get-filename -o "%(playlist_index)s %(title)s %(id)s.%(ext)s" '$link' >> "$name".m3u)); - notify( "Finished downloading $name by $artist", - "Downloaded $link" ); - }; - } - elsif ( $choice eq 'Play' ) { - $choice = choose(['Audio (queue in mpd)', 'Audio (mpv)']); - if ( $choice eq 'Audio (queue in mpd)' ) { - detach sub { - system(qq([ -d "$HOME/.cache/mpd" ] || mkdir -p "$HOME/.cache/mpd"; - title=\$(youtube-dl --ignore-config --get-title "$link" 2>/dev/null); - printf "%b\n" "\$title\t$link" >> "$HOME/.cache/mpd/linkarchive";)); - system(qq(mpc add "\$(youtube-dl -x -g '$link')")); - }; - } - elsif ( $choice eq 'Audio (mpv)' ) { - system(qq(mpv --no-audio-display --no-video --volume=50 '$link')); - } - } +# How to play {{{1 +sub play_audio_mpd { + my $link = shift; + detach sub { + system(qq([ -d "$HOME/.cache/mpd" ] || mkdir -p "$HOME/.cache/mpd"; + title=\$(youtube-dl --ignore-config --get-title "$link" 2>/dev/null); + printf "%b\n" "\$title\t$link" >> "$HOME/.cache/mpd/linkarchive";)); + system(qq(mpc add "\$(youtube-dl -x -g '$link')")); + }; } -elsif (endswith_strs(['mkv', 'webm', 'mp4']) or includes_strs(['youtube.com/watch', 'youtube.com/playlist', 'yewtu.be', 'youtu.be', 'hooktube.com', 'bitchute.com', 'videos.lukesmith.xyz', 'v.redd.it', 'fb.watch', 'vimeo.com'])) { - $choice = choose(['Play', 'Open', 'Download']); - if ( $choice eq 'Open' ) { - system( 'open', $link ); + +sub play_audio_mpv { + my $link = shift; + system(qq(mpv --no-audio-display --no-video --volume=50 '$link')); +} + +sub play_video_mpv { + my $link = shift; + detach sub { + system(qq(mpvq '$link' >/dev/null 2>&1)); + notify 'Starting mpv', "Opening $link..."; } - elsif ( $choice eq 'Download' ) { - my $download_dir = "$HOME/Downloads"; - $choice = choose([ 'Both', 'Audio', 'Video' ]); - if ( $choice eq 'Both' ) { +} + +# How to download {{{1 +sub download_bandcamp { + my $link = shift; + detach sub { + my $download_dir = "$HOME/Downloads/songs/listen to"; + system(qq(mkdir -p "$download_dir")); + chdir($download_dir); + ( my $name = $link ) =~ s!^.*/!!; + ( my $artist = $link ) =~ s|https*://||; + $artist =~ s/\.bandcamp\.com.*//; + + notify( "Downloading $name by $artist", "Downloading $link" ); + system(qq(mkdir -p $artist)) unless ( -d $artist ); + chdir($artist); + system(qq(mkdir -p $name)) unless ( -d $name ); + chdir($name); + system qq(youtube-dl -f mp3 -o "%(playlist_index)s %(title)s %(id)s.%(ext)s" '$link' >/dev/null 2>&1); + system(qq(printf "#EXTM3U\n#PLAYLIST:%s\n#EXTART:%s\n" "$name" "$artist" > "$name".m3u)); + system(qq(youtube-dl -f mp3 --get-filename -o "%(playlist_index)s %(title)s %(id)s.%(ext)s" '$link' >> "$name".m3u)); + notify( "Finished downloading $name by $artist", + "Downloaded $link" ); + }; +} + +sub download_audio { + my $link = shift; + detach sub { + my $download_dir = "$HOME/Downloads/songs/listen to"; + notify 'Download (audio) started', "Downloading $link"; + system(qq(youtube-dl --add-metadata -xic -f bestaudio/best -o "$download_dir/%(title)s-%(creator)s.%(ext)s" --exec "notify 'Download finished' 'Downloaded $link.' linkhandler" '$link' >/dev/null 2>&1)); + }; +} + +sub download_video { + my $link = shift; + my $download_dir = "$HOME/Downloads"; + + my %choices = ( + 'Both' => sub { + my $link = shift; detach sub { notify 'Download (av) started', "Downloading $link"; system(qq(youtube-dl --add-metadata -ic --write-sub --embed-subs -o "$download_dir/%(title)s-%(creator)s.%(ext)s" --exec "notify 'Download finished' 'Downloaded $link.' linkhandler" '$link' >/dev/null 2>&1)); }; - } - elsif ( $choice eq 'Audio' ) { - detach sub { - notify 'Download (audio) started', "Downloading $link"; - system(qq(youtube-dl --add-metadata -xic -f bestaudio/best -o "$download_dir/%(title)s-%(creator)s.%(ext)s" --exec "notify 'Download finished' 'Downloaded $link.' linkhandler" '$link' >/dev/null 2>&1)); - }; - } - elsif ( $choice eq 'Video' ) { + }, + 'Audio' => \&download_audio, + 'Video' => sub { + my $link = shift; detach sub { notify 'Download (video) started', "Downloading $link"; system(qq(youtube-dl --add-metadata -ic -f bestvideo --write-sub --embed-subs -o "$download_dir/%(title)s-%(creator)s.%(ext)s" --exec "notify 'Download finished' 'Downloaded $link.' linkhandler" '$link' >/dev/null 2>&1)); }; } - } - elsif ( $choice eq 'Play' ) { - $choice = choose(['Video', 'Audio (queue in mpd)', 'Audio (mpv)']); - if ( $choice eq 'Video' ) { - detach sub { - system(qq(mpvq '$link' >/dev/null 2>&1)); - notify 'Starting mpv', "Opening $link..."; - } - } - elsif ( $choice eq 'Audio (queue in mpd)' ) { - detach sub { - system(qq([ -d "$HOME/.cache/mpd" ] || mkdir -p "$HOME/.cache/mpd"; - title=\$(youtube-dl --ignore-config --get-title "$link" 2>/dev/null); - printf "%b\n" "\$title\t$link" >> "$HOME/.cache/mpd/linkarchive";)); - system(qq(mpc add "\$(youtube-dl -x -g '$link')")); - }; - } - elsif ( $choice eq 'Audio (mpv)' ) { - system(qq(mpv --no-audio-display --no-video --volume=50 '$link')); + ); + my $selected_ref = menu(\%choices); + $selected_ref->($link); +} + +# Main {{{1 +if (is_bandcamp($LINK)) { + my %choices = ( + 'Download' => \&download_bandcamp, + 'Play' => sub { + my $link = shift; + my %choices = ( + 'Audio (queue in mpd)' => \&play_audio_mpd, + 'Audio (mpv)' => \&play_audio_mpv + ); + + my $selected_ref = menu(\%choices); + $selected_ref->($link); } - } + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } -elsif ( endswith_strs( [ 'png', 'jpg', 'jpe', 'jpeg', 'gif' ] ) ) { - detach sub { - notify 'Starting image viewer', "Opening $link..."; - system(qq(curl -sL '$link' >"/tmp/\$(printf "%s" '$link' | sed "s/.*\\///")")); - system(qq(opener "/tmp/\$(printf "%s" '$link' | sed "s/.*\\///")" >/tmp/error 2>&1)); - } +elsif (is_video($LINK)) { + my %choices = ( + 'Play' => sub { + my $link = shift; + + my %choices = ( + 'Video' => \&play_video_mpv, + 'Audio (queue in mpd)' => \&play_audio_mpd, + 'Audio (mpv)' => \&play_audio_mpv + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); + }, + 'Download' => \&download_video + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } -elsif ( endswith_strs( ['gifv'] ) ) { - detach sub { - system(qq(mpv --volume=50 '$link' >/dev/null 2>&1)); - }; +elsif (is_image($LINK)) { + my %choices = ( + 'View (nsxiv)' => sub { + my $link = shift; + detach sub { + notify 'Starting image viewer', "Opening $link..."; + system(qq(curl -sL '$link' >"/tmp/\$(printf "%s" '$link' | sed "s/.*\\///")")); + system(qq(opener "/tmp/\$(printf "%s" '$link' | sed "s/.*\\///")" >/tmp/error 2>&1)); + } + } + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } -elsif ( endswith_strs(['mp3', 'flac', 'opus', 'mp3?source']) or includes_strs(['soundcloud.com'])) { - $choice = choose( [ 'Download', 'Play' ] ); - if ( $choice eq 'Download' ) { - # TODO - } - elsif ( $choice eq 'Play' ) { - $choice = choose(['Audio (queue in mpd)', 'Audio (mpv)']); - if ( $choice eq 'Audio (queue in mpd)' ) { +elsif (is_gifv($LINK)) { + my %choices = ( + 'View (mpv)' => sub { + my $link = shift; detach sub { - system(qq([ -d "$HOME/.cache/mpd" ] || mkdir -p "$HOME/.cache/mpd"; - title=\$(youtube-dl --ignore-config --get-title "$link" 2>/dev/null); - printf "%b\n" "\$title\t$link" >> "$HOME/.cache/mpd/linkarchive";)); - system(qq(mpc add "\$(youtube-dl -x -g '$link')")); + system(qq(mpv --volume=50 '$link' >/dev/null 2>&1)); }; } - elsif ( $choice eq 'Audio (mpv)' ) { - system(qq(mpv --no-audio-display --no-video --volume=50 '$link')); - } - } - + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } -elsif ( includes_strs( ['reddit.com'] ) ) { - system(qq(reddio print -c always "comments/\$(printf "%s" '$link' | cut -d/ -f7)" | less -+F -+X)); -} -elsif ( startswith_strs( [ 'http://', 'https://' ] ) ) { - if (($TERM eq 'dumb') or (not defined $TERM)) { - system(qq(alacritty -e w3m -config ~/.config/w3m/config -T text/html '$link')); - } - else { - system(qq(w3m -config ~/.config/w3m/config -T text/html '$link')); - } +elsif (is_audio($LINK)) { + my %choices = ( + 'Download' => \&download_audio, + 'Play' => sub { + my $link = shift; + my %choices = ( + 'Audio (queue in mpd)' => \&play_audio_mpd, + 'Audio (mpv)' => \&play_audio_mpv + ); + + my $selected_ref = menu(\%choices); + $selected_ref->($link); + } + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } -elsif ( startswith_strs( ['!http'] ) ) { - notify 'Open in browser incomplete', 'not yet'; +elsif (checkstr($LINK, 'includes', ['reddit.com'])) { + my %choices = ( + 'reddio' => sub { + my $link = shift; + # Have to go via bash here to be able to pipe to `less` + launch_in_terminal(qq(bash -c 'reddio print -c always "comments/\$(printf "%s" '$link' | cut -d/ -f7)" | less -+F -+X')); + } + ); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } -elsif ( startswith_strs( ['@http'] ) ) { - notify 'copy incomplete', 'not yet'; +elsif (checkstr($LINK, 'starts', ['http://', 'https://'])) { + my %choices = ( + 'w3m' => sub { + my $link = shift; + launch_in_terminal(qq(w3m -config ~/.config/w3m/config -T text/html '$link')); + }); + my $selected_ref = menu(\%choices); + $selected_ref->($LINK); } else { - if ( -f $link ) { - system(qq(\${EDITOR:-vim} '$link')); + if ( -f $LINK ) { + system(qq(\${EDITOR:-vim} '$LINK')); } else { - system(qq(open '$link' >/dev/null 2>&1)); + system(qq(open '$LINK' >/dev/null 2>&1)); } }