From 04446011f364909096cb49f2b66e4df96d713209 Mon Sep 17 00:00:00 2001 From: Guillermo Ramos Date: Tue, 13 Apr 2021 18:17:14 +0200 Subject: Initial commit --- bm | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100755 bm (limited to 'bm') diff --git a/bm b/bm new file mode 100755 index 0000000..4573135 --- /dev/null +++ b/bm @@ -0,0 +1,231 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use File::Basename qw; +use File::Find qw; +use File::Glob qw<:bsd_glob :nocase>; +use File::Path qw; +use File::Which qw; +use IPC::Open2 qw; +use List::Util qw; + +use JSON qw; + +my $BMDIR = "$ENV{HOME}/.bm"; +mkdir $BMDIR || die $!; + +my $YT = which('yt'); +my $GIT = "git -C '$BMDIR'" if which('git') && -d "$BMDIR/.git"; + +sub is_uri { + my $text = shift; + return $text =~ /^http/; +} + +sub all_contents { + my @files; + my $pre_len = length $BMDIR; + find({wanted => sub { + if (substr($_, 0, 1) eq '.' && $File::Find::name ne $BMDIR) { + # Ignore hidden files/dirs (except $BMDIR) + $File::Find::prune = 1; + } elsif (-f) { + push @files, $File::Find::name; + } + }}, + $BMDIR); + @files = sort { $a cmp $b } @files; + return ([], \@files); +} + +sub dir_contents { + my $dir = shift; + + my @dirs = $dir eq $BMDIR ? () : ('..'); + my @files; + + foreach (glob "$dir/*") { + if (-d) { + push @dirs, $_; + } elsif (-f) { + push @files, $_; + } + } + + return (\@dirs, \@files); +} + +sub read_url { + my $file = shift; + open(my $fh, '<', $file) || die $!; + my $url = <$fh>; + return unless $url && is_uri($url); + $url =~ s/\s*$//g; + return $url; +} + +sub open_bm { + my $url = shift; + if ($YT && $url =~ /youtube\.com\/watch/) { + `yt $url`; + return; + } + my $focused_name = `xdotool getwindowfocus getwindowname`; + if ($focused_name =~ / (Firefox|Chrom(e|ium))$/) { + `xdotool - <<<"key ctrl+t\ntype --delay 1ms $url\nsleep 0.2\nkey Return Tab"`; + } else { + `xdg-open $url`; + } +} + +sub url_domain { + my $url = shift; + my ($domain) = $url =~ /^[a-z]+:\/\/([^\/]+)/; + return $domain; +} + +sub notify { + my ($urgency, $text) = @_; + `notify-send -u '$urgency' '$text'`; +} + +sub save_bm { + my ($basedir, $text) = @_; + my ($uri, $title); + if (is_uri($text)) { + $uri = $text; + $title = `rofi -dmenu -p "Set title" -no-fixed-num-lines` or return; + $title =~ s/\n//g; + } else { + $uri = `rofi -dmenu -p "Set URL" -no-fixed-num-lines` or return; + $uri =~ s/\n//g; + if (is_uri($uri)) { + $title = $text; + } else { + notify('critical', 'Not an URL; skipping'); + return; + } + } + my $path = "$basedir/$title"; + make_path(dirname($path)); + open(my $fh, '>',$path); + print $fh "$uri\n"; + if ($GIT) { + `$GIT add '$path'`; + my $basename = basename($path); + `$GIT commit -m "Add bookmark '$basename'"`; + } + notify('normal', "Bookmark saved"); +} + +sub sanitize_rofi { + return shift =~ s/&/&/gr; +} + +sub draw_dir_entry { + my $dir = shift; + return "" . sanitize_rofi(basename($dir)) . ""; +} + +sub draw_file_entry { + my $file = shift; + my $url = read_url($file); + unless ($url) { + print "$file does not seem to contain a valid URL; skipping\n"; + return; + } + return sanitize_rofi(basename($file)) . " " . url_domain($url) . ""; +} + +sub select_bm { + my $basedir = $BMDIR; + my $recursive = 0; + while (-d $basedir) { + my ($dirs, $files) = $recursive ? all_contents() : dir_contents($basedir); + + my $msg = $recursive ? "All bookmarks" : "Open bookmark"; + open2(my $outh, my $inh, "rofi -sync -p '$msg' -dmenu -multi-select -i -markup-rows -lines 40 -width 80 -format 'i,f'"); + print $inh "$_\n" foreach (map { draw_dir_entry($_) } @$dirs); + print $inh "$_\n" foreach (map { draw_file_entry($_) } @$files); + close $inh; + + # Get all output lines from rofi + my @out = map { [/^(-?\d+),(.*)$/] } <$outh> or die 'cancelled'; + + # If there are sereval selections, all of them must be files + if (all { $_->[0] > $#$dirs } @out) { + foreach my $out (@out) { + my ($idx, $input) = @$out; + my $url = read_url($files->[$idx - @$dirs]); + open_bm($url); + } + return; + } elsif (@out == 1) { + my ($idx, $input) = @{$out[0]}; + if ($idx == -1) { + # User entered something which was not among the options + if ($input) { + # If there was something in the search bar, save it + save_bm($basedir, $input); + return; + } else { + # Otherwise, take it as the recursive toggle (C-Ret) + $recursive = ! $recursive; + $basedir = $BMDIR; + } + } elsif ($idx <= $#$dirs) { + # User selected a directory + my $dir = $dirs->[$idx]; + if ($dir eq '..') { + $basedir = dirname($basedir); + } else { + $basedir .= '/' . basename($dir); + } + } + } + } +} + +sub sanitize_path { + my $path = shift; + $path =~ s/\//-/g; + $path = substr($path, 0, 100); + #$path =~ s/\(/-/g; + return $path; +} + +sub import_firefox { + my ($basedir, $json) = @_; + if ($json->{type} eq "text/x-moz-place-container") { + my $title = sanitize_path($json->{title}); + my $newdir = "$basedir/$title"; + mkdir $newdir; + import_firefox($newdir, $_) foreach (@{$json->{children}}); + } elsif ($json->{type} eq "text/x-moz-place") { + my $uri = $json->{uri}; + my $title = sanitize_path($json->{title}); + my $newfile = "$basedir/$title"; + open(my $fh, '>', $newfile) || print "Unable to open $newfile\n"; + print $fh $uri; + } +} + +if (@ARGV == 0) { + select_bm(); +} elsif ($ARGV[0] eq "-h") { + die "Usage: $0 [--import-firefox ] [git ]\n"; +} elsif ($ARGV[0] eq "git") { + if ($GIT) { + shift; + exec 'git', '-C', $BMDIR, @ARGV; + } else { + die which('git') ? "$BMDIR is not a git repository\n" : "git is not installed\n"; + } +} elsif ($ARGV[0] eq "--import-firefox") { + open(my $fh, '<', $ARGV[1]); + my $json = decode_json(<$fh>); + close($fh); + import_firefox($BMDIR, $json); +} -- cgit v1.2.3