dotfiles

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

linkhandler (13894B)


      1 #!/usr/bin/env perl
      2 # vim: foldmethod=marker
      3 use strict;
      4 use warnings;
      5 use 5.006; # checked with `perlver`
      6 
      7 use constant EXIT_USER_CANCELLED => 130;
      8 use constant MPD_HOST => 'localhost';
      9 use constant MPD_PORT => 6600;
     10 
     11 sub urlize {
     12   my ($rv) = @_;
     13   $rv =~ s/([^A-Za-z0-9])/sprintf("%%%2.2X", ord($1))/ge;
     14   return $rv;
     15 }
     16 
     17 sub un_urlize {
     18   my ($rv) = @_;
     19   $rv =~ s/\+/ /g;
     20   $rv =~ s/%(..)/pack("c",hex($1))/ge;
     21   return $rv;
     22 }
     23 
     24 sub untrack {
     25   my ($orig_link) = @_;
     26   if ($orig_link =~ /(https?(?::|%3A)(?:%2F%2F|\/\/).*)/) {
     27     $orig_link = un_urlize($1);
     28   }
     29   $orig_link =~ s/https?(?::|%3A)(?:%2F%2F|\/\/).*(https?(?::|%3A)(?:%2F%2F|\/\/).*)/$1/;
     30   $orig_link =~ s/utm_[^&]*&?//g;
     31   $orig_link =~ s/\?$//;
     32 
     33   if ($orig_link =~ /lnks\.gd\// or $orig_link =~ /url.*\.creators\.gumroad\.com/) {
     34     $orig_link = `curl --silent --head --write-out '%{redirect_url}' --output /dev/null "$orig_link"`;
     35   }
     36   return $orig_link;
     37 }
     38 
     39 my $CHOOSER = $ENV{'CHOOSER'};
     40 unless (length $CHOOSER) {
     41   die 'Must set CHOOSER in environment.';
     42 }
     43 
     44 my $HOME = $ENV{'HOME'};
     45 my $TERM = $ENV{'TERM'};
     46 
     47 if ( @ARGV != 1 ) {
     48   die 'Link necessary.';
     49 }
     50 my ($LINK) = @ARGV;
     51 $LINK = untrack($LINK);
     52 
     53 =begin
     54 This subroutine takes a subroutine reference and executes the dereferenced subroutine in a fork.
     55 Basically a way to handle things async by just writing `detach sub { .... }` in code.
     56 Using a fork is OK here because: "If a parent process terminates, then its
     57 "zombie" children (if any) are adopted by init(8), which automatically performs
     58 a wait to remove the zombies."
     59 # https://linux.die.net/man/2/wait
     60 (Also, what a sentence. Fun without the context.)
     61 =cut
     62 sub detach {
     63   my $funcref = shift;
     64   if ( fork() == 0 ) {
     65     $funcref->();
     66   }
     67   return;
     68 }
     69 
     70 # Runs arguments in surrounding terminal, or creates a new one.
     71 sub launch_in_terminal {
     72   my $args = shift;
     73   if (($TERM eq 'dumb') or (not defined $TERM)) {
     74     system(qq(alacritty -e $args));
     75   }
     76   else {
     77     system($args);
     78   }
     79   return;
     80 }
     81 
     82 # Send notification via my cross-platform-ish `notify` script
     83 sub notify {
     84   my ($title, $message) = @_;
     85   system qq(notify '$title' '$message' linkhandler >/dev/null 2>&1);
     86   return;
     87 }
     88 
     89 # String checking functions {{{1
     90 # Checks if $str (includes|endswith|startswith) any of $substrs
     91 sub checkstr {
     92   my ($str, $mode, $substrs) = @_;
     93   my @result;
     94   if ($mode eq 'includes') {
     95     @result = grep { index( $str, $_ ) != -1 } @{$substrs};
     96   }
     97   elsif ($mode eq 'ends') {
     98     @result = grep { $str =~ /\Q$_\E$/msx } @{$substrs};
     99   }
    100   elsif ($mode eq 'starts') {
    101     @result = grep { $str =~ /^\Q$_\E/msx } @{$substrs};
    102   }
    103   return scalar @result;
    104 }
    105 
    106 sub is_bandcamp_track {
    107   my $link = shift;
    108   return (checkstr($link, 'includes', ['bandcamp.com', 'godisanastronaut.com']) and checkstr($link, 'includes', ['/track/']));
    109 }
    110 
    111 sub is_bandcamp_album {
    112   my $link = shift;
    113   return (checkstr($link, 'includes', ['bandcamp.com', 'godisanastronaut.com']) and checkstr($link, 'includes', ['/album/']));
    114 }
    115 
    116 sub is_video {
    117   my $link = shift;
    118   return (checkstr($link, 'ends', ['mkv', 'webm', 'mp4'])
    119       or checkstr($link, 'includes', ['youtube.com/watch', 'youtube.com/playlist', 'yewtu.be', 'youtu.be',
    120         'hooktube.com', 'bitchute.com', 'videos.lukesmith.xyz', 'v.redd.it', 'fb.watch', 'vimeo.com']));
    121 }
    122 sub is_image {
    123   my $link = shift;
    124   return checkstr($link, 'ends',  ['png', 'jpg', 'jpe', 'jpeg', 'gif']);
    125 }
    126 
    127 sub is_gifv {
    128   my $link = shift;
    129   return checkstr($link, 'ends', ['gifv']);
    130 }
    131 
    132 sub is_audio {
    133   my $link = shift;
    134   return (checkstr($link, 'ends', ['mp3', 'flac', 'opus', 'mp3?source'])
    135       or checkstr($link, 'includes', ['soundcloud.com']));
    136 }
    137 
    138 # Menu builders {{{1
    139 # Choose from @options (passed by reference) via $CHOOSER
    140 sub choose {
    141   my $options_ref = shift;
    142   my $options_str = join "\n", @{$options_ref};
    143   my $selected = `printf '$options_str\n' | "$CHOOSER"`;
    144   $selected =~ s/\s+$//msx;
    145   unless (length $selected) {
    146     exit EXIT_USER_CANCELLED; # user interrupted
    147   }
    148   return $selected;
    149 }
    150 
    151 # Receives a dispatch table of strings to subroutines, as an array.
    152 # Lets you select one of the strings via `choose`.
    153 # Returns reference to corresponding subroutine.
    154 sub menu {
    155   my $tableref = shift;
    156 
    157   # Global system open -- should be available for every link
    158 
    159   push @$tableref, ['Open (system)', sub {
    160     my ($link) = @_;
    161     system('open', $link);
    162   }];
    163 
    164   # Global copy -- should be available for every link
    165   push @$tableref, ['Copy', sub {
    166     my ($link) = @_;
    167     system(qq(printf '%s' '$link' | clc));
    168     notify 'Copied to clipboard', $link;
    169   }];
    170 
    171   push @$tableref, ['Save', sub {
    172       my ($link) = @_;
    173       detach sub {
    174         system(qq(pocket save '$link'));
    175         notify 'Saved to Pocket', $link;
    176       }
    177     }];
    178 
    179   push @$tableref, ['Archive', sub {
    180     my ($link) = @_;
    181     detach sub {
    182       web_archive($link);
    183       notify "Archived", "$link";
    184     }
    185   }];
    186 
    187   push @$tableref, ['Archive (and copy archived)', sub {
    188     my ($link) = @_;
    189     detach sub {
    190       my $location = web_archive($link);
    191       system(qq(printf '%s' '$location' | clc));
    192       notify "Archived $link", "Archived location copied to clipboard: $location";
    193     }
    194   }];
    195 
    196   push @$tableref, ['Send via KDEConnect', sub {
    197     my ($link) = @_;
    198     detach sub {
    199       system(qq(kdeconnect-handler "$link"));
    200     }
    201   }];
    202 
    203 
    204   push @$tableref, ['Create QR code', sub {
    205     my ($link) = @_;
    206     detach sub {
    207       system(qq(qrencode -d 300 -t png "$link" -o /tmp/qr.png));
    208       system(qq(nsxiv /tmp/qr.png));
    209     }
    210   }];
    211 
    212   push @$tableref, ['Noecho News', sub {
    213     my ($link) = @_;
    214     detach sub {
    215       notify 'Finding different perspectives...', "$link";
    216       launch_in_terminal(qq(article-noecho-news "$link"));
    217     }
    218   }];
    219 
    220   push @$tableref, ['Quicksilver', sub {
    221     my ($link) = @_;
    222     detach sub {
    223       system(qq(printf '%s' '$link' | qs));
    224     }
    225   }];
    226 
    227   # Choose an option
    228   my @options = map { $_->[0] } @$tableref;
    229   my $choice = choose(\@options);
    230 
    231   # Return corresponding the subroutine reference
    232   my $choice_index = (grep { $options[$_] eq $choice } 0..$#options)[0];
    233   return $tableref->[$choice_index][1];
    234 }
    235 
    236 # Save in the Internet Archive
    237 sub web_archive {
    238   my $link = shift;
    239   my $location = `curl -sI 'https://web.archive.org/save/$link' | awk -F': ' '/^location/ { print \$2 }'`;
    240   return $location;
    241 }
    242 
    243 
    244 # How to play {{{1
    245 sub play_audio_mpd {
    246   my $link = shift;
    247 
    248   detach sub {
    249     if (checkstr($link, 'includes', ['youtube.com/playlist'])) {
    250       system(qq([ -d "$HOME/.cache/mpd" ] || mkdir -p "$HOME/.cache/mpd"));
    251       open(my $playlist, '-|', qq(youtube-dl --flat-playlist -j '$link' | jq -r '.url')) or die "Couldn't read playlist $link";
    252       while (my $song = <$playlist>) {
    253         chomp $song;
    254         system(qq(
    255           title=\$(youtube-dl --ignore-config --get-title '$song' 2>/dev/null);
    256           printf "%b\n" "\$title\t$song" >> "$HOME/.cache/mpd/linkarchive";
    257 
    258           send_mpd_command() {
    259             (printf '%s\n' "\$1"; sleep 1) | telnet ${\MPD_HOST} ${\MPD_PORT} 2>/dev/null
    260           }
    261 
    262           url="\$(youtube-dl -x -g "$song")"
    263           res="\$(send_mpd_command "addid \$url")"
    264           song_id="\$(printf '%s' "\$res" | awk -F': ' '/^Id: / { print \$2 }')"
    265           send_mpd_command "\$(printf 'addtagid %s Artist "%s"\naddtagid %s Title "%s"' "\$song_id" "Youtube" "\$song_id" "\$title")" >/dev/null));
    266       }
    267       close($playlist) or die "Could not close handle.";
    268 
    269       notify 'Added playlist', $link;
    270     }
    271     else {
    272       system(qq([ -d "$HOME/.cache/mpd" ] || mkdir -p "$HOME/.cache/mpd";
    273         title=\$(youtube-dl --ignore-config --get-title "$link" 2>/dev/null);
    274         printf "%b\n" "\$title\t$link" >> "$HOME/.cache/mpd/linkarchive";
    275 
    276         send_mpd_command() {
    277           (printf '%s\n' "\$1"; sleep 1) | telnet ${\MPD_HOST} ${\MPD_PORT} 2>/dev/null
    278         }
    279 
    280         url="\$(youtube-dl -x -g "$link")"
    281         res="\$(send_mpd_command "addid \$url")"
    282         song_id="\$(printf '%s' "\$res" | awk -F': ' '/^Id: / { print \$2 }')"
    283         send_mpd_command "\$(printf 'addtagid %s Artist "%s"\naddtagid %s Title "%s"' "\$song_id" "Youtube" "\$song_id" "\$title")" >/dev/null));
    284     };
    285   }
    286 }
    287 
    288 sub play_audio_mpv {
    289   my $link = shift;
    290   system(qq(mpv --no-audio-display --no-video --volume=50 '$link'));
    291 }
    292 
    293 sub play_video_mpv {
    294   my $link = shift;
    295   detach sub {
    296     system(qq(mpvq '$link' >/dev/null 2>&1));
    297     notify 'Starting mpv', "Opening $link...";
    298   }
    299 }
    300 
    301 # How to download {{{1
    302 sub download_bandcamp {
    303   my $link = shift;
    304   detach sub {
    305     my $download_dir = "$HOME/Downloads/songs/listen to";
    306     system(qq(mkdir -p "$download_dir"));
    307     chdir($download_dir);
    308     ( my $name = $link ) =~ s!^.*/!!;
    309     ( my $artist = $link ) =~ s|https*://||;
    310     $artist =~ s/\.bandcamp\.com.*//;
    311 
    312     notify( "Downloading $name by $artist", "Downloading $link" );
    313     system(qq(mkdir -p $artist)) unless ( -d $artist );
    314     chdir($artist);
    315     system(qq(mkdir -p $name)) unless ( -d $name );
    316     chdir($name);
    317     system qq(youtube-dl -f mp3 -o "%(playlist_index)s %(title)s %(id)s.%(ext)s" '$link' >/dev/null 2>&1);
    318     system(qq(printf "#EXTM3U\n#PLAYLIST:%s\n#EXTART:%s\n" "$name" "$artist" > "$name".m3u));
    319     system(qq(youtube-dl -f mp3 --get-filename -o "%(playlist_index)s %(title)s %(id)s.%(ext)s" '$link' >> "$name".m3u));
    320     notify( "Finished downloading $name by $artist",
    321       "Downloaded $link" );
    322   };
    323 }
    324 
    325 sub download_audio {
    326   my $link = shift;
    327   detach sub {
    328     my $download_dir = "$HOME/Downloads/songs/listen to";
    329     notify 'Download (audio) started', "Downloading $link";
    330     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));
    331   };
    332 }
    333 
    334 sub download_video {
    335   my $link = shift;
    336   my $download_dir = "$HOME/Downloads";
    337 
    338   my @choices = (
    339     ['Both', sub {
    340       my $link = shift;
    341       detach sub {
    342         notify 'Download (av) started', "Downloading $link";
    343         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));
    344       };
    345     }],
    346     ['Audio', \&download_audio],
    347     ['Video', sub {
    348       my $link = shift;
    349       detach sub {
    350         notify 'Download (video) started', "Downloading $link";
    351         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));
    352       };
    353     }]
    354   );
    355   my $selected_ref = menu(\@choices);
    356   $selected_ref->($link);
    357 }
    358 
    359 # Main {{{1
    360 if (is_bandcamp_track($LINK)) {
    361   my @choices = (
    362     ['Download', \&download_bandcamp],
    363     ['Play', sub {
    364       my $link = shift;
    365       my @choices = (
    366         ['Audio (queue in mpd)', \&play_audio_mpd],
    367         ['Audio (mpv)', \&play_audio_mpv]
    368       );
    369 
    370       my $selected_ref = menu(\@choices);
    371       $selected_ref->($link);
    372     }]
    373   );
    374   my $selected_ref = menu(\@choices);
    375   $selected_ref->($LINK);
    376 }
    377 elsif (is_bandcamp_album($LINK)) {
    378   my @choices = (
    379     ['Download', \&download_bandcamp],
    380     ['Play', sub {
    381       my $link = shift;
    382       my @choices = (
    383         # ['Audio (queue in mpd)', \&play_audio_mpd],
    384         ['Audio (mpv)', \&play_audio_mpv]
    385       );
    386 
    387       my $selected_ref = menu(\@choices);
    388       $selected_ref->($link);
    389     }]
    390   );
    391   my $selected_ref = menu(\@choices);
    392   $selected_ref->($LINK);
    393 }
    394 elsif (is_video($LINK)) {
    395   my @choices = (
    396     ['Play', sub {
    397       my $link = shift;
    398 
    399       my @choices = (
    400         ['Video', \&play_video_mpv],
    401         ['Audio (queue in mpd)', \&play_audio_mpd],
    402         ['Audio (mpv)', \&play_audio_mpv]
    403       );
    404       my $selected_ref = menu(\@choices);
    405       $selected_ref->($LINK);
    406     }],
    407     ['Download', \&download_video]
    408   );
    409   my $selected_ref = menu(\@choices);
    410   $selected_ref->($LINK);
    411 }
    412 elsif (is_image($LINK)) {
    413   my @choices = (
    414     ['View (nsxiv)', sub {
    415       my $link = shift;
    416       detach sub {
    417         notify 'Starting image viewer', "Opening $link...";
    418         system(qq(curl -sL '$link' >"/tmp/\$(printf "%s" '$link' | sed "s/.*\\///")"));
    419         system(qq(opener "/tmp/\$(printf "%s" '$link' | sed "s/.*\\///")" >/tmp/error 2>&1));
    420       }
    421     }]
    422   );
    423   my $selected_ref = menu(\@choices);
    424   $selected_ref->($LINK);
    425 }
    426 elsif (is_gifv($LINK)) {
    427   my @choices = (
    428     ['View (mpv)', sub {
    429       my $link = shift;
    430       detach sub {
    431         system(qq(mpv --volume=50 '$link' >/dev/null 2>&1));
    432       };
    433     }]
    434   );
    435   my $selected_ref = menu(\@choices);
    436   $selected_ref->($LINK);
    437 }
    438 elsif (is_audio($LINK)) {
    439   my @choices = (
    440     ['Download', \&download_audio],
    441     ['Play', sub {
    442       my $link = shift;
    443       my @choices = (
    444         ['Audio (queue in mpd)', \&play_audio_mpd],
    445         ['Audio (mpv)', \&play_audio_mpv]
    446       );
    447 
    448       my $selected_ref = menu(\@choices);
    449       $selected_ref->($link);
    450     }]
    451   );
    452   my $selected_ref = menu(\@choices);
    453   $selected_ref->($LINK);
    454 }
    455 elsif (checkstr($LINK, 'includes',  ['reddit.com'])) {
    456   my @choices = (
    457     ['reddio', sub {
    458       my $link = shift;
    459       # Have to go via bash here to be able to pipe to `less`
    460       launch_in_terminal(qq(bash -c 'reddio print -c always "comments/\$(printf "%s" '$link' | cut -d/ -f7)" | less -+F -+X'));
    461     }]
    462   );
    463   my $selected_ref = menu(\@choices);
    464   $selected_ref->($LINK);
    465 }
    466 elsif (checkstr($LINK, 'starts', ['http://', 'https://'])) {
    467   my @choices = (
    468     ['w3m', sub {
    469       my $link = shift;
    470       launch_in_terminal(qq(w3m -config ~/.config/w3m/config -T text/html '$link'));
    471     }]);
    472   my $selected_ref = menu(\@choices);
    473   $selected_ref->($LINK);
    474 }
    475 else {
    476   if ( -f $LINK ) {
    477     system(qq(\${EDITOR:-vim} '$LINK'));
    478   }
    479   else {
    480     system(qq(open '$LINK' >/dev/null 2>&1));
    481   }
    482 }