diff --git a/.local/bin/fzf/fzf_menu_run b/.local/bin/fzf/fzf_menu_run new file mode 100755 index 000000000..779bc225d --- /dev/null +++ b/.local/bin/fzf/fzf_menu_run @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# fzf_menu_run +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 10 March 2021, 12:34:47 PM [GMT+1] +#Modified: +# Thu 20 October 2022, 05:48:00 AM [GMT+1] +# +#Description: +# +# +#Dependencies: +# devour, fzf +# +# shellcheck disable=all + +programs=$(compgen -c | sort -u | tail -n +9) +cmd=$(echo -e "$programs" | fzf --prompt="Run Program: " --border=rounded --margin=1% --color=dark --height 100% --reverse --header=" PROGRAM MENU " --info=hidden --header-first) +exec gobble $cmd \ No newline at end of file diff --git a/.local/bin/fzf/fzf_music b/.local/bin/fzf/fzf_music new file mode 100755 index 000000000..83ada44c9 --- /dev/null +++ b/.local/bin/fzf/fzf_music @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# fzf_music +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 10 March 2021, 12:34:47 PM [GMT] +#Modified: +# Mon 21 November 2022, 07:44:23 AM [GMT] +# +#Description: +# fzf music player +# +#Dependencies: +# fzf +# + +REPOMENU_MUSICPLAYER="ffplay -nodisp -loglevel quiet" + +asksetting() { + playlist=$* + if [[ -z ${playlist} ]]; then + for Song in "$HOME"/Music/*; do + if [ -f "$Song" ]; then + Name=${Song##*/} + case $Name in + *.mp3 | *.flac | *.wav | .ogg) + options+=${Song##*/}$'\n' + ;; + esac + fi + done + else + for Song in "$playlist"/*; do + if [ -f "$Song" ]; then + Name=${Song##*/} + case $Name in + *.mp3 | *.flac | *.wav | *.ogg) + options+=${Song##*/}$'\n' + ;; + esac + fi + done + fi + echo -e "${options::-1} + +Close music player" | fzf --prompt="Song Search: " --border=rounded --margin=1% --color=dark --height 100% --reverse --header=" MUSIC MENU " --info=hidden --header-first +} + +LOOPSETTING="true" +while [ -n "$LOOPSETTING" ]; do + CHOICE="$(asksetting "$1")" + [ -n "$CHOICE" ] || exit + unset LOOPSETTING + case $CHOICE in + *.mp3 | *.flac | *.wav | *.ogg) + playlist=$* + if [[ -z ${playlist} ]]; then + folder=$HOME/Music + else + folder=$playlist + fi + $REPOMENU_MUSICPLAYER "$folder/$CHOICE" & + ;; + *Close*) + pkill "${REPOMENU_MUSICPLAYER%% *}" & + ;; + *) + echo "Program terminated." && exit + ;; + esac +done diff --git a/.local/bin/fzf/fzf_pass b/.local/bin/fzf/fzf_pass new file mode 100755 index 000000000..9416f4751 --- /dev/null +++ b/.local/bin/fzf/fzf_pass @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# -*-coding:utf-8 -*- +# Auto updated? +# Yes +#File : +# fzf_pass +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +# Created: +# Wed 10 March 2021, 12:34:47 PM [GMT] +# Modified: +# Thu 20 October 2022, 06:02:31 AM [GMT+1] +# +# Description: +# fzf +# +# shellcheck disable=all + +shopt -s nullglob globstar + +typeit=0 +if [[ $1 == "--type" ]]; then + typeit=1 + shift +fi +xdotool="xsel -b" + + +STARTDIR=${PASSWORD_STORE_DIR-~/.password-store} +BASEDIR=$STARTDIR +DONE=0 +LEVEL=0 +PREVSELECTION="" +SELECTION="" +URL_FIELD='url' +LOGIN_FIELD='login' + +while [ "$DONE" -eq 0 ] ; do + password_files=( "$STARTDIR"/* ) + password_files=( "${password_files[@]#"$STARTDIR"/}" ) + password_files=( "${password_files[@]%.gpg}" ) + + if [ "$LEVEL" -ne 0 ] ; then + password_files=(".." "${password_files[@]}") + fi + entry=$(printf ' %s\n' "${password_files[@]}" | fzf --prompt="Password Search: " --border=rounded --margin=1% --color=dark --height 100% --reverse --header=" PASSWORD MENU " --info=hidden --header-first) + entry=$(echo $entry | sed "s/ //") + + if [ -z "$entry" ] ; then + DONE=1 + exit + fi + + if [ "$entry" != ".." ] ; then + PREVSELECTION=$SELECTION + SELECTION="$SELECTION/$entry" + + # check if another dir + if [ -d "$STARTDIR/$entry" ] ; then + STARTDIR="$STARTDIR/$entry" + LEVEL=$((LEVEL+1)) + else + # not a directory so it must be a real password entry + + if [[ $typeit -eq 0 ]]; then + FIELDS=() + PASSWORD=$(pass show "$SELECTION" | head -1 | sed '/^$/d') + OTHER=$(pass show "$SELECTION" | awk -F: '(NR>1){ st = index($0,":");print $1 substr($0,st+1)}') + FIELDS+=" Password\n" + if [ ! -z "${OTHER}" ]; then + while read -r line; do + FIELD=$(echo -e "$line" | awk '{print $1}') + if [ "$FIELD" = "login" ]; then + FIELDS+=" ${FIELD^}\n" + elif [ "$FIELD" = "url" ]; then + FIELDS+="爵 ${FIELD^^}\n" + else + FIELDS+=" ${FIELD^}\n" + fi + done <<< "$OTHER" + fi + + ENTRY_FIELD=$(echo -e "$FIELDS" | sed '/^$/d' | fzf --prompt="Password Settings: " --border=rounded --margin=1% --color=dark --height 100% --reverse --header=" PASSWORD MENU " --info=hidden --header-first) + if [[ $ENTRY_FIELD = *'Password' ]]; then + PASSWORD=$(pass show "$SELECTION" | sed "s/^[ \t]*//" | head -1 | sed '/^$/d') + echo -n "$PASSWORD" | $xdotool + elif [[ $ENTRY_FIELD = *'URL' ]]; then + URL=$(pass show "$SELECTION" | grep "${URL_FIELD}" | awk '{sub(/:/,"")}{print $2}1' | sed "s/^[ \t]*//" | head -1 | sed '/^$/d') + echo -n "$URL" | $xdotool + elif [[ $ENTRY_FIELD = *'Login' ]]; then + LOGIN=$(pass show "$SELECTION" | grep "${LOGIN_FIELD}" | awk '{sub(/:/,"")}{print $2}1' | sed "s/^[ \t]*//" | head -1 | sed '/^$/d') + echo -n "$LOGIN" | $xdotool + else + CUSTOM_FIELD=$(echo -e "$ENTRY_FIELD" | awk 'NF>1 {sub("^[^A-Z]*","")} {print}') + CUSTOM_FIELD=$(pass show "$SELECTION" | grep "${CUSTOM_FIELD,,}" | awk '{sub(/:/,"")}{first = $1; $1=""; print $0;}1' | sed "s/^[ \t]*//" | head -1 | sed '/^$/d') + echo -n "$CUSTOM_FIELD" | $xdotool + fi + else + xdotool - <<<"type --clearmodifiers -- $(pass show "$SELECTION" | head -n 1 | sed '/^$/d')" + fi + DONE=1 + fi + +else + LEVEL=$((LEVEL-1)) + SELECTION=$PREVSELECTION + STARTDIR="$BASEDIR/$SELECTION" + fi +done + + + diff --git a/.local/bin/fzf/fzf_powermenu b/.local/bin/fzf/fzf_powermenu new file mode 100755 index 000000000..0f4f26786 --- /dev/null +++ b/.local/bin/fzf/fzf_powermenu @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# fzf_powermenu +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 10 March 2021, 12:34:47 PM [GMT+1] +#Modified: +# Tue 01 November 2022, 06:45:08 AM [GMT] +# +#Description: +# +# +#Dependencies: +# fzf +# +# shellcheck disable=all + +getuptime() { + uptime -p >/dev/null 2>&1 + + if [ "$?" -eq 0 ]; then + # Supports most Linux distro + # when the machine is up for less than '0' minutes then + # 'uptime -p' returns ONLY 'up', so we need to set a default value + UP_SET_OR_EMPTY=$(uptime -p | awk -F 'up ' '{print $2}') + UP=${UP_SET_OR_EMPTY:-'less than a minute'} + else + # Supports Mac OS X, Debian 7, etc + UP=$(uptime | sed -E 's/^[^,]*up *//; s/mins/minutes/; s/hrs?/hours/; + s/([[:digit:]]+):0?([[:digit:]]+)/\1 hours, \2 minutes/; + s/^1 hours/1 hour/; s/ 1 hours/ 1 hour/; + s/min,/minutes,/; s/ 0 minutes,/ less than a minute,/; s/ 1 minutes/ 1 minute/; + s/ / /; s/, *[[:digit:]]* users?.*//') + fi + + echo "$UP" +} + +asksetting() { + options=" Lock +望 Sleep + Logout + Restart +襤 Shutdown" + + echo -e "Uptime: $(getuptime) +$options" | fzf --prompt="Power Settings: " --border=rounded --margin=1% --color=dark --height 100% --reverse --header=" POWER MENU " --info=hidden --header-first +} + +triggerFunction() { + init_system="$(cat /proc/1/comm)" + if [[ $init_system = "systemd" ]]; then + systemctl "$1" + elif [[ $init_system = "init" ]]; then + loginctl "$1" + elif [[ $init_system = "runit" ]]; then + loginctl "$1" + else + systemctl "$1" + fi +} + +LOOPSETTING="true" +while [ -n "$LOOPSETTING" ]; do + CHOICE="$(asksetting "$@")" + [ -n "$CHOICE" ] || exit + unset LOOPSETTING + case "$CHOICE" in + *Logout) + if [[ "$DESKTOP_SESSION" == "i3" ]]; then + i3-msg exit + elif [[ "$DESKTOP_SESSION" == "herbstluftwm" ]]; then + herbstclient quit + else + pkill -KILL -u "$USER" + fi + ;; + *Lock) + multimonitorlock -l -- --time-str="%I:%M:%S %p" + ;; + *Shutdown) + triggerFunction poweroff + ;; + *Restart) + triggerFunction reboot + ;; + *Sleep) + triggerFunction suspend + ;; + *) + echo "Program terminated." && exit 1 + ;; + esac +done diff --git a/.local/bin/fzf/fzf_run b/.local/bin/fzf/fzf_run new file mode 100755 index 000000000..e6cb1be97 --- /dev/null +++ b/.local/bin/fzf/fzf_run @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# fzf_run +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 10 March 2021, 12:34:47 PM [GMT+1] +#Modified: +# Sat 05 November 2022, 04:25:20 PM [GMT] +# +#Description: +# +# +#Dependencies: +# bash +# +# shellcheck disable=all + +## Script metadata +SCRIPTNAME=${0##*/} +DESCRIPTION="A bash and fzf run script for terminal." +AUTHOR="The-Repo-Club " + +command='' +width='' +height='' +instance='' +term='' + +#=== COLORS =================================================================== + +## Set Colors (copied from makepkg) +b_blk="\e[0;90m" +b_red="\e[0;91m" +b_grn="\e[0;92m" +b_yel="\e[0;93m" +b_blu="\e[0;94m" +b_mag="\e[0;95m" +b_cyn="\e[0;96m" +b_wht="\e[0;97m" +blk="\e[0;30m" +red="\e[0;31m" +grn="\e[0;32m" +yel="\e[0;33m" +blu="\e[0;34m" +mag="\e[0;35m" +cyn="\e[0;36m" +wht="\e[0;37m" +bld="\e[1;1m" +del="\e[0;0m" + +readonly b_blk b_red b_grn b_yel b_blu b_mag b_cyn b_wht blk red grn yel blu mag cyn wht bld del + +#=== FUNCTION ================================================================= +# Name: error +# Description: Print message with a red pretag an ERROR +# Parameter 1: Message to print +#============================================================================== + +# copied from makepkg +error() { + local mesg=$1 + printf "${red}${bld}==> ERROR:${wht} %b${del}\n" "$mesg" +} + +run_program() { + if command -v $command &>/dev/null; then + cmd="xterm" + if [[ -n $term ]]; then + cmd="$term" + fi + + if [[ -n $instance ]]; then + cmd+=" --class=$instance" + fi + + if [[ -n $command ]]; then + cmd+=" -e $command" + fi + exec $cmd >&/dev/null + exit 0 + fi +} + +show_help() { + printf " + %b + Usage: + %b... + %b [options]... + Options: + -h, --help Display help. + -c, --command Set the command to be ran.. + -i, --instance Set the WM_CLASS for the program thats ran. + -t, --term Override the terminal you would like to use. + \n" "$DESCRIPTION" "$SCRIPTNAME" "$SCRIPTNAME" + exit 0 +} + +while [[ "$1" ]]; do + case $1 in + --command | -c) + shift + command=$1 + ;; + --instance | -i) + shift + instance=$1 + ;; + --term | -t) + shift + term=$1 + ;; + --help | -h) + show_help + exit + ;; + -*) + error 'Incorrect option(s) specified.' + show_help + ;; + *) + error 'Incorrect option(s) specified.' + show_help + ;; + esac + shift +done +run_program +error 'Incorrect option(s) specified.' +show_help + diff --git a/.local/bin/fzf/fzf_youtube_subs b/.local/bin/fzf/fzf_youtube_subs new file mode 100755 index 000000000..abbfbdb24 --- /dev/null +++ b/.local/bin/fzf/fzf_youtube_subs @@ -0,0 +1,269 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# fzf_youtube_subs +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Sun 03 January 2021, 05:09:33 PM [GMT] +#Modified: +# Thu 20 October 2022, 03:27:18 PM [GMT+1] +# +#Description: +# Watch your youtube subscriptions without a youtube account +# via curl, fzf, browser and basic unix commands. +# +# The $SUBS_FILE is a text file containing usernames or channel IDs +# comments and blank lines are ignored. +# +# +#Dependencies: +# fzf +# + +fzf_menu() { + fzf --prompt="Select a video: " --border=rounded --margin=1% --color=dark --height 100% --reverse --header=" YOUTUBE SUBS MENU " --info=hidden --header-first +} + +# -/-/-/-/- Settings -/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/ +: "${SUBS_FILE:=${HOME}/.config/fzf/subs.ini}" +: "${SUBS_MENU_PROG:=fzf_menu}" +: "${SUBS:=${HOME}/.cache/subs}" +: "${SUBS_LINKS:=$SUBS/links}" +: "${SUBS_OPEN:=$(gobble videoplayer)}" +: "${SUBS_CACHE:=$SUBS/cache}" +: "${SUBS_SLEEP_VALUE:=0.05}" # raise this if you experience problems +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SEP=^^^^^ # shouldn't need to change this +# -/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/ + +die() { + printf >&2 '%s\n' "$*" + exit 1 +} + +usage() { + die 'Usage: fzf_youtube_subs [-c cat_subs] [-g gen_links] [-u update_subs] [-d daemonize]' +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Synopsis: $SUBS_FILE [txt] -> $SUBS_LINKS [xml links] +# +# Updates local cache of xml subscription links from the +# subscription file containing either usernames or channel ids. +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +gen_links() { + : >"$SUBS_LINKS" + + count=0 + total=$(sed -e '/^$/d' -e '/^#/d' <"$SUBS_FILE" | wc -l) + + while read -r line; do + + # ignore comments and blank lines + case $line in '' | ' ' | '#'*) continue ;; esac + + # strip off in-line comments and any trailing whitespace + line=${line%%#*} + line=${line%% *} + + count=$((count + 1)) + + case $line in + UC*) + # YT channel IDs always begin with 'UC' and are 24 chars long + printf "[%s/%s] using channel ID '%s' for xml link\n" "$count" "$total" "$line" + + [ ${#line} -eq 24 ] && + printf 'https://youtube.com/feeds/videos.xml?%s\n' \ + "channel_id=$line" >>"$SUBS_LINKS" + ;; + *) + # otherwise we are given a username, we must find out its channel ID + printf "fetching channel ID for %s...\n" "$line" + + curl -sfL --retry 10 "https://youtube.com/user/$line/about" | + while read -r line; do + case $line in + *channel/UC??????????????????????*) + line=${line##*channel/} + line=${line%%\"*} + printf "[%s/%s] using channel ID '%s' for xml link\n" "$count" "$total" "$line" + printf 'https://youtube.com/feeds/videos.xml?channel_id=%s\n' \ + "$line" >>"$SUBS_LINKS" + break + ;; + esac + done & + sleep "${SUBS_SLEEP_VALUE:-0}" + ;; + esac + + done <"$SUBS_FILE" + + count=0 + while [ "$count" -ne "$total" ]; do + count=$(wc -l <"$SUBS_LINKS") + printf "[%s/%s] waiting for jobs to complete...\n" "$count" "$total" + sleep 0.5 + done +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Synopsis: $1 [LINK] -> $SUBS_CACHE/$chan_name/concat [CHANNEL INFO] +# +# Takes a channel rss feed link and creates a file +# with a line of its videos dates, titles, and urls. +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +get_vids() { + data=$(curl -sfL --retry 15 "$1") + + # hide the first tag which is the channel + # creation date + data=${data#*\<\/published\>} + + # trim off outer tags + chan=${data%%} + + printf "%s\n" "$data" | + while read -r line; do + case $line in + *'link rel='*) + line=${line#*href=\"} + line=${line%\"/\>} + line=https://${line#*www.} + url=$line + ;; + *''*) + line=${line%+00:*} + line=${line#*} + date=$line + ;; + *''*) + line=${line%} + title=$line + printf '%s\n' \ + "${date}${SEP}${chan}${SEP}${title}${SEP}${url}" \ + >>"$SUBS_CACHE/$chan" + ;; + esac + done +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Updates the local cache of subscriptions. ([-u] flag) +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +update_subs() { + [ -f "$SUBS_LINKS" ] || die 'Subs links have not been generated.' + + rm -r "${SUBS_CACHE:-?}" 2>/dev/null || : + mkdir -p "$SUBS_CACHE" + + total=$(wc -l <"$SUBS_LINKS") + + count=0 + while read -r link; do + count=$((count + 1)) + printf 'starting job [%s/%s] for %s\n' "$count" "$total" "$link" + get_vids "$link" & + sleep "${SUBS_SLEEP_VALUE:-0}" + done <"$SUBS_LINKS" + + count=0 + while [ "$count" -ne "$total" ]; do + count=$(printf '%s\n' "$SUBS_CACHE"/* | wc -l) + printf "[%s/%s] waiting for fetch jobs to complete...\n" "$count" "$total" + sleep 0.5 + done + + printf '%s\n\n' 'done!' +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Grab current cache of subscriptions, sort by date uploaded +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +cat_subs() { + sort -r "$SUBS_CACHE"/* | + while read -r line; do + chan=${line#*$SEP} + chan=${chan%%$SEP*} + title=${line#*$chan$SEP} + title=${title%%$SEP*} + date=${line%%$SEP*} + date=${date#*-} + date=${date%T*} + printf '[%s %s] %s\n' "$date" "$chan" "$title" + done +} + +# Split the concatenated lines into entities, send to menu program. +# Finally, play the result with mpv. +get_sel() { + if [ -d "$SUBS_CACHE" ]; then + sel=$(cat_subs | $SUBS_MENU_PROG) + else + die 'Subs cache has not been retrieved.' + fi + + [ "$sel" ] || die Interrupted + + chan="${sel#* }" + chan="${chan%%] *}" + title=${sel#*"$chan"\] } + while read -r line; do + case $line in + *"$SEP$title$SEP"*) + url=${line##*$SEP} + if [ "$url" ]; then + printf 'playing: %s\n' "$url" + # Play the selection. + # shellcheck disable=2086 + exec devour $SUBS_OPEN "$url" + fi + break + ;; + esac + done <"$SUBS_CACHE/$chan" +} + +daemonize() { + # create a cached copy of the subs file to check for changes + # if changes occur, re-generate links automatically + daemon_file=${HOME}/.cache/subs_daemon.cache + if [ ! -f "$daemon_file" ]; then + cp -f "${SUBS_FILE:=${HOME}/.config/fzf/subs.ini}" "$daemon_file" + fi + + while true; do + if ! cmp "${SUBS_FILE:=${HOME}/.config/fzf/subs.ini}" "$daemon_file"; then + cp -f "${SUBS_FILE:=${HOME}/.config/fzf/subs.ini}" "$daemon_file" + fi + gen_links + update_subs + interval=${SUBS_DAEMON_INTERVAL:-$((10 * 60))} + printf 'Sleeping for %s seconds...\n' "$interval" + sleep "$interval" + done +} + +main() { + mkdir -p "$SUBS" + + case ${1#-} in + h) usage ;; + g) gen_links ;; + u) update_subs ;; + c) cat_subs ;; + d) daemonize ;; + *) get_sel ;; + esac +} + +main "$@" diff --git a/.local/bin/rofi/README.md b/.local/bin/rofi/README.md new file mode 100644 index 000000000..1084dc9a6 --- /dev/null +++ b/.local/bin/rofi/README.md @@ -0,0 +1,125 @@ +# Bookmark Manager + +## Usage + +```help +Usage: ./bm [modifier(s)] command [option(s)] + + Commands : + ========== + -h Print this help + -H Print help for legacy usage + -v/-V Print the version + -a 'URL' Add the URL to bookmark file + Options for -a + -t "TagList" Tags are sparated by a comma , + -T "Title" Title for this URL (if empty and allowed Title + downloaded) + -A "accel" Accelerator when search for URLs (start with Accel) + Default is FQDN without gTld (and www, and sheme) + -F Force the bookmark to be created (even if duplicate + or invalid) + -p Force the screenshot to be taken + + -l List all URLs (default action, same thing as calling bm without args) + Options for -l + -z Use the alternate print list + -n Sort the results by date + -N Sort the results by date (reverse) + + + -s 'object' Search for bookmarks + Options for -s + -i Incensitive case search + -z Use the alternate print list + -n Sort the results by date + -N Sort the results by date (reverse) + + -o 'object' Search for bookmarks and open it (use the same argument as for -s) + Options for -o + -i Incensitive case search + -O If more than one answer force the first bookmark to be open + -Y If more than one answer force ALL bookmarks to be open + + -x 'object' Search for bookmarks and copy it to clipboard (use the same argument as for -s) + Options for -x + -i Incensitive case search + -X If more than one answer force the first bookmark to be copied + -Y If more than one answer force ALL bookmarks to be copied + + -r 'object' Search for bookmarks and Print the recorded associated picture (use the same argument as for -s) + Options for -r + -i Incensitive case search + -O If more than one answer force the first bookmark to have its picture printed + -Y If more than one answer force ALL bookmarks to have their picture printed + + -d 'URL' or Delete the URL from bookmark file md5sum or 'URL part' + Options for -d + -D Delete first occurence only + -F Force the bookmark to be deleted (even if duplicate) + -p Delete the associated picture (no trash available) + + -g Generate a HTML page with all bookmarks + If used more than once, generate a page per tag + Options for -g + -G "filename" If g==1 then generate then use this filename to generate page + -O Open the file when generated + + -P 'object' Generate all Non existant picture (check done for all URL in bm), if none argument. + If an argument is given (use the same argument as for -s) only the results will have a picture. + Options for -P + -F Force the picture to be taken again (even if already exists). + + -L List all tags + + -C Print the color table (usefull for configuration) + + -S Show statistics about bookmarks/tags (and also configuration) + Options for -S + -p Print the list of orphaned Pictures + + Modifiers : + =========== + -c 'file' Use this configuration file instead of the default one + + -b 'file' Use this bookmark file instead of the default one + + Objects: + ======== + :string Search in accelerator list + +string Search in tags list + =string Search in MD5 list + /string Search in URL list + string Search in full test + + How to use: + =========== + # add a bookmark with the given url, description, and optional tags + $ bm add [desc] [tag...] [accelerator] + + # open the first bookmark matching + $ bm open + $ bm + + # search the bookmarks via full-text + $ bm search + + # list tags + $ bm tags + + # list bookmarks available + $ bm list + $ bm ls + $ bm + + # display statistics about the bookmarks + $ bm statistics + $ bm stats + + # view bookmark screenshots in your default browser + $ bm view design + $ bm view + + # clear all bookmarks + $ bm clear +``` \ No newline at end of file diff --git a/.local/bin/rofi/bm b/.local/bin/rofi/bm new file mode 100755 index 000000000..db15d27ad --- /dev/null +++ b/.local/bin/rofi/bm @@ -0,0 +1,925 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# bm +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Fri 09 December 2022, 07:19:23 AM [GMT] +#Modified: +# Fri 14 July 2023, 11:47:25 PM [GMT+1] +# +#Description: +# +# +#Dependencies: +# +# +# shellcheck disable=all + +# default filename for Baker config file (just the filename no path) +readonly _BM_DEFAULT_CONFIG_FILENAME="${_BM_DEFAULT_CONFIG_FILENAME:=bm.conf}" + +config() { + echo "Reading Configuration..." + [[ "${__w:=$HOME}" != "${HOME}" ]] && echo "Working dir changed to ${__w}" + [[ "${__w: -1}" != '/' ]] && __w="${__w//\/\//\/}/" + if [ ${__B:=0} -eq 0 ]; then + # If a config file is given, we take it first, or look for a default config file + export _BM_CONFIG_FILE="${__w}${_BM_DEFAULT_CONFIG_FILENAME}" + if [ "$(uname -s)" = 'Linux' -a -f "$(readlink -m "${1:-}" 2>/dev/null)" ]; then + export _BM_CONFIG_FILE="${1}" + . "${1}" + elif [ -e "${1}" ]; then + export _BM_CONFIG_FILE="${1}" + . "${1}" + elif [ ${__n:=0} -eq 0 -a -f "${__w}${_BM_DEFAULT_CONFIG_FILENAME}" ]; then + . "${__w}${_BM_DEFAULT_CONFIG_FILENAME}" + echo "Default config file loaded ${__w}${_BM_DEFAULT_CONFIG_FILENAME}" + fi + else + export _BM_CONFIG_FILE='none' + fi + # _BM_BOOKMARK_FILE : Where is stored the bookmark file + export _BM_BOOKMARK_FILE="${_BM_BOOKMARK_FILE:=${__w}bm.bm}" + + # _BM_BOOKMARK_BACKUP_FILE : Where is stored the backup of bookmark file + export _BM_BOOKMARK_BACKUP_FILE="${_BM_BOOKMARK_BACKUP_FILE:=${__w}.bm.bm.bck}" + + # _BM_HTML_FILE : Location of generated HTML file + export _BM_HTML_FILE="${_BM_HTML_FILE:=${__w}bm.html}" + + # _BM_DELETE_FILE : Where to find the deleted URLs + export _BM_DELETE_FILE="${_BM_DELETE_FILE:=${__w}.bm.trash}" + + # _BM_BOOKMARK_BACKUP : Copy the bookmark file before each modification + export _BM_BOOKMARK_BACKUP=${_BM_BOOKMARK_BACKUP:=false} + + # _BM_BOOKMARK_BACKUP_VERSION : How many copy to keep (0 for 1, 1 for 2, ...) + export _BM_BOOKMARK_BACKUP_VERSION=${_BM_BOOKMARK_BACKUP_VERSION:=0} + + # _BM_DELETE_ALLOWED : Even with the force flag the delete function will not be allowed (true or false) + export _BM_DELETE_ALLOWED=${_BM_DELETE_ALLOWED:=true} + + # _BM_DELETE_TO_FILE : Allow or disallow the deleted URLs to be copied in the Trash (true or false) + export _BM_DELETE_TO_FILE=${_BM_DELETE_TO_FILE:=false} + + # _BM_DELETE_PICTURE : Allow or disallow the picture associated to a URL to be deleted (true or false) + export _BM_DELETE_PICTURE=${_BM_DELETE_PICTURE:=true} + + # _BM_DUPS_DETECT : Look for duplicates URL before insert in bookmark file (ovverided by -F) + export _BM_DUPS_DETECT=${_BM_DUPS_DETECT:=true} + + # _BM_ASK_BEFORE_OPEN : When using bm with a URL (or search term) ask before opening the browser if more than one answer + export _BM_ASK_BEFORE_OPEN=${_BM_ASK_BEFORE_OPEN:=true} + + # _BM_GET_PAGE_TITLE : When adding a URL execute a curl command to get the if none given + export _BM_GET_PAGE_TITLE=${_BM_GET_PAGE_TITLE:=true} + + # _BM_SCREENSHOT_DIRECTORY : Where to store the screenshot taken + export _BM_SCREENSHOT_DIRECTORY="${_BM_SCREENSHOT_DIRECTORY:=${__w}.bm.shots}" + + # _BM_SCREENSHOT_GET : When adding a URL try to take a screenshot in background + export _BM_SCREENSHOT_GET=${_BM_SCREENSHOT_GET:=true} + + # _BM_SCREENSHOT_WAIT : When taking a screenshot wait for the action to finish + export _BM_SCREENSHOT_WAIT=${_BM_SCREENSHOT_WAIT:=false} + + # _BM_OPEN_FIRST : When using open, the first link found is open + # Also work with the copy to clipboard function + export _BM_OPEN_FIRST=${_BM_OPEN_FIRST:=false} + + # _BM_OPEN_ALL : When using open, if more than one result, all are open (with the same function) + # Also work with the copy to clipboard function (overrided by -Y) + export _BM_OPEN_ALL=${_BM_OPEN_ALL:=false} + + # _BM_NO_ARGS_FORCE_HELP : If no args given to script sends to help instead of list all link + export _BM_NO_ARGS_FORCE_HELP=${_BM_NO_ARGS_FORCE_HELP:=false} + + # _BM_CREATE_BOOKMARK_FILE : If bm is called to list and no file exists. bm will create a default one. + export _BM_CREATE_BOOKMARK_FILE=${_BM_CREATE_BOOKMARK_FILE:=true} + + # _BM_CMD_CAPTURE_CHECK : If you don't have tool installed to take screenshot, just put to false. + # It will disable the _BM_GET_SCREENSHOT. + export _BM_CMD_CAPTURE_CHECK=${_BM_CMD_CAPTURE_CHECK:=true} + + # I suppose the uname command is available + # Please note, the PRE & POST capture command will not be analyzes nor verified. + if [ "$(uname -s)" = 'Linux' ]; then + export _BM_CMD_IMAGE_OPEN='feh' + export _BM_CMD_OPEN='xdg-open' + export _BM_CMD_PRE_CAPTURE='' + export _BM_CMD_CAPTURE='cutycapt' + export _BM_CMD_CAPTURE_ARGS='--insecure --url={URL} --out={FILE}' + export _BM_CMD_POST_CAPTURE='' + export _BM_CMD_MD5='md5sum' + export _BM_CMD_COPY='xsel' + else + # To keep commpatibility with the Mac (even i don't have any) + export _BM_CMD_IMAGE_OPEN='open' + export _BM_CMD_OPEN='open' + export _BM_CMD_PRE_CAPTURE='' + export _BM_CMD_CAPTURE='webkit2png' + export _BM_CMD_CAPTURE_ARGS='-C -o {FILE}.png {URL}' + export _BM_CMD_POST_CAPTURE='cp -- "{FILE}-clipped.png" "{FILE}" ; rm -f -- {FILE}-{clipped,full,thumb}.png' + export _BM_CMD_MD5='md5' + export _BM_CMD_COPY='xsel' + fi + + # _BM_PRINT_LINE : This line is used to print datas + export _BM_PRINT_LINE="${_BM_PRINT_LINE:=${BOLD}${GRAY}BM_ACCEL ${RESET}${DIM}-->${RESET} ${GRAY_LIGHT}BM_TITLE ${GRAY_DARK}${BOLD}[BM_TAGS]${RESET}\n${UNDERLINE}${DIM}BM_URL${RESET} - ${GRAY_DARK}Added: BM_DATE${RESET}}" + + # _BM_PRINT_LINE_ALTERNATIVE : This line is used to print datas + export _BM_PRINT_LINE_ALTERNATIVE="${_BM_PRINT_LINE_ALTERNATIVE:=${BOLD}${GRAY}BM_ACCEL ${RESET}${DIM}-->${RESET} ${GRAY_LIGHT}BM_TITLE ${GRAY_DARK}${BOLD}[BM_TAGS]${RESET}\n${UNDERLINE}${DIM}BM_URL${RESET} ${GRAY_DARK}(BM_MD5)${RESET} - ${GRAY_DARK}Added: BM_DATE${RESET} - ${GRAY_DARK}Picture: BM_PICTURE${RESET}}" + + # _BM_PRINT_SCHEME : If set to no, bm will remove scheme from URL printing + export _BM_PRINT_SCHEME=${_BM_PRINT_SCHEME:=true} + + # _BM_SEARCH_IGNORECASE : Ignore case if set when searching (overrided by -i) + export _BM_SEARCH_IGNORECASE=${_BM_SEARCH_IGNORECASE:=false} +} + +# slug creates a friendly URL like 'hello-world' +slug() { + iconv -f utf8 -t ascii//TRANSLIT | tr -cs '[:alnum:]\n' - | tr '[:upper:]' '[:lower:]' | sed 's|^-*||;s|-*$||' +} + +# version +RELEASE='$Format:%h$' +VERSION='2021.04.05' + +# +# Output usage info +# + +usage() { +cat <<EOF + +Usage: ${0} [modifier(s)] command [option(s)] + + Commands : + ========== + -h Print this help + -v/-V Print the version/And the short commit name + -a 'URL' Add the URL to bookmark file + Options for -a + -t "TagList" Tags are sparated by a comma , + -T "Title" Title for this URL (if empty and allowed Title + downloaded) + -A "accel" Accelerator when search for URLs (start with Accel) + Default is an autoincrement index + -F Force the bookmark to be created (even if duplicate + or invalid) + -p Force the screenshot to be taken (config dependant) + + -l List all URLs (default action, same thing as calling bm without args) + Options for -l + -z Use the alternate print list + -n Sort the results by date + -N Sort the results by date (reverse) + + -s 'object' Search for bookmarks + Options for -s + -i Incensitive case search + -z Use the alternate print list + -n Sort the results by date + -N Sort the results by date (reverse) + Objects are + :string Search in accelerator list + +string Search in tags list + =string Search in MD5 list + /string Search in URL list + string Search in full test + + -o 'object' Search for bookmarks and open it (use the same argument as for -s) + Options for -o + -i Incensitive case search + -O If more than one answer force the first bookmark to be open + -Y If more than one answer force ALL bookmarks to be open + + -x 'object' Search for bookmarks and copy it to clipboard (use the same argument as for -s) + Options for -x + -i Incensitive case search + -X If more than one answer force the first bookmark to be copied + -Y If more than one answer force ALL bookmarks to be copied + + -r 'object' Search for bookmarks and Print the recorded associated picture (use the same argument as for -s) + Options for -r + -i Incensitive case search + -O If more than one answer force the first bookmark to have its picture printed + -Y If more than one answer force ALL bookmarks to have their picture printed + + -d 'URL' Delete the URL from bookmark file + md5sum or + 'URL part' Options for -d + -D Delete first occurence only + -F Force the bookmark to be deleted (even if duplicate) + -p Delete the associated picture (no trash available) + + -g Generate a HTML page with all bookmarks + If used more than once, generate a page per tag + Options for -g + -G "filename" If g==1 then generate and use this filename to generate page + -O Open the file when generated + + -P 'object' Generate all Non existant picture (check done for all URL in bm), if none argument. + If an argument is given (use the same argument as for -s) only the results will have a picture. + Options for -P + -F Force the picture to be taken again (even if already exists). + + -q 'object' Connect to the URL to replace the existing Title by the downloaded one. + Options for -q + -i Incensitive case search + -O If more than one answer force the first bookmark to have its title downloaded + -Y If more than one answer force ALL bookmarks to have their title downloaded + + -L List all tags + + -C Print the color table (usefull for configuration) + + -E Open the bm.bm with your \$EDITOR + + -S Show statistics about bookmarks/tags (and also configuration) + Options for -S + -p Print the list of orphaned Pictures + + Modifiers : + =========== + -c 'file' Use this configuration file instead of the default one + + -b 'file' Use this bookmark file instead of the default one + + -w 'directory' Use this directory to find default configuration file and bookmark file instead of the default one +EOF +} + +getAccelMax() { + while read a; do + (( ${a:=0} >= ${_max:=0} )) && (( _max=${a}+1 )) + done <<< "$( awk -F'|' '$0 !~ /^[ ]*#/ && $3 ~ /^[:space:]*:[:space:]*[0-9]+[:space:]*$/ { sub(/:+/, "", $3); print $3; }' "${_BM_BOOKMARK_FILE}")" + echo -n "${_max}" +} + +backupBm() { + if [ "${_BM_BOOKMARK_BACKUP,,}" = 'true' ]; then + while [ ${count:=${_BM_BOOKMARK_BACKUP_VERSION}} -gt 0 ]; do + cp -- "${_BM_BOOKMARK_BACKUP_FILE}.$(( count-1 ))" "${_BM_BOOKMARK_BACKUP_FILE}.$(( count-- ))" 2>&1 >/dev/null + done + cp -- "${_BM_BOOKMARK_FILE}" "${_BM_BOOKMARK_BACKUP_FILE}.0" + fi +} + +titleDl() { + if command -v hxselect &>/dev/null ; then + curl -Lks "${1:-}" 2>&1 | hxselect -ic title 2>/dev/null + else + curl -Lks "${1:-}" 2>&1 | sed '/<title>/I!d;/<\/title>/I!d;s;^[[:space:]]*<title>\([^<]*\)<.*;\1;i' + fi +} + +saveUrl() { + [[ "${__url}" =~ ^[[:space:]]*(f|ht)tps*://.*$ ]] || { + if [ ${__F:=0} -eq 0 ]; then + die "Your URL seems invalid '${__url}'. Use -F to force." + else + echo "Force adding invalid URL" >&2 + fi + } +local _sum="$(${_BM_CMD_MD5} <<< "${__url}")" +if [ "${_BM_DUPS_DETECT,,}" = 'true' ]; then + if [ ! -z "$(awk -F '|' '$0 !~ /^[ ]*#/ && $1 ~ /'"${_sum%% *}"'/' "${_BM_BOOKMARK_FILE}")" ]; then + if [ ${__F:=0} -eq 0 ]; then + die "URL is already in the Bookmark file" + else + echo "Force adding duplicate URL" >&2 + fi + fi +fi +if [ "${_BM_GET_PAGE_TITLE,,}" = 'true' -a -z "${__T:=}" ]; then + # if [ $( command -v hxselect &>/dev/null ) ]; then + # __T="$( curl -Lks "${__url}" 2>&1 | hxselect -ic title )" + # else + # __T="$( curl -Lks "${__url}" 2>&1 | sed '/<title>/I!d;/<\/title>/I!d;s;^[[:space:]]*<title>\([^<]*\)<.*;\1;i' )" + # fi + __T="$( titleDl "${__url}" )" + fi + [[ ! -z "${__T:=}" ]] && __T="${__T//\|/\\|}" + backupBm + [[ -z "${__T:=}" ]] && __T="$(slug <<< "${__url}")" # If no title => slug the url + [[ -z "${__t:=}" ]] && __t='default' # Default tag is default + [[ -z "${__A:=}" ]] && __A="$(getAccelMax)" + [[ "${__A:0:1}" != ':' ]] && __A=":${__A}" # If no accel => Add a number + echo "${_sum%% *}|$(date '+%FT%TZ')|${__A}|${__url}|${__T}|${__t}" >> "${_BM_BOOKMARK_FILE}" || die "Insert aborted into '${_BM_BOOKMARK_FILE}' !" + echo "${__url} inserted into '${_BM_BOOKMARK_FILE}'" + [[ ! -f "${_BM_SCREENSHOT_DIRECTORY}/${_sum%% *}.png" || ${__F:=0} -eq 1 ]] && { screenshot_take "${_BM_SCREENSHOT_DIRECTORY}/${_sum%% *}.png" "${__url}" & } + [[ "${_BM_SCREENSHOT_WAIT,,}" = 'true' ]] && wait + search_bookmarks "${__A}" + } + +# +# manage each line to print it +# + +readLines() { + IFS='|' + while read m d a u T t; do + _line="${_BM_PRINT_LINE}" + [[ ${__z:=0} -eq 1 ]] && _line="${_BM_PRINT_LINE_ALTERNATIVE}" + _line="${_line//BM_MD5/$m}" + if [ "${_BM_PRINT_SCHEME,,}" = 'false' ]; then + u="$(sed 's/^[[:space:]]*\(f\|ht\)tps*:\/\///' <<< "${u}")" + fi + _line="${_line//BM_URL/$u}" + _line="${_line//BM_TITLE/$T}" + _line="${_line//BM_TAGS/$t}" + _line="${_line//BM_ACCEL/${a:=No-Accelerator}}" + _line="${_line//BM_DATE/${d}}" + local _pict="$(stat -c '%y' "${_BM_SCREENSHOT_DIRECTORY}/${m}.png" 2>/dev/null)" + [[ ! -z "${_pict}" ]] && _pict="$(date '+%FT%TZ' --date "${_pict}")" + _line="${_line//BM_PICTURE/${_pict:=None}}" + echo -e "${_line}\n" + done +} + +# +# List all bookmarks +# + +list_bookmarks() { + [[ ${__n:=0} -eq 1 ]] && { awk '$0 !~ /^[ ]*#/' "${_BM_BOOKMARK_FILE}" | sort -t'|' -k2 | readLines; return; } + [[ ${__N:=0} -eq 1 ]] && { awk '$0 !~ /^[ ]*#/' "${_BM_BOOKMARK_FILE}" | sort -t'|' -k2 -r | readLines; return; } + awk '$0 !~ /^[ ]*#/' "${_BM_BOOKMARK_FILE}" | readLines +} + +# +# Search all bookmarks with <query> +# +search() { + local _s='' + [[ -z "${1:-}" ]] && list_bookmarks && return + case "${1:0:1}" in + :) _s=3; _ss=0 ;; # Accelerator + +) _s="NF"; _ss=1 ;; # Tags + =) _s=1; _ss=1 ;; # MD5 part + /) _s=4; _ss=1 ;; # URL part + *) _s=0; _ss=0 ;; # full text search + esac + [[ "${_BM_SEARCH_IGNORECASE,,}" = 'true' || ${__i:=0} -eq 1 ]] && local _ign=1 + awk -F'|' -vIgn=${_ign:=0} ' + BEGIN { + if (Ign == 1) { s=tolower("'"${1:${_ss}}"'"); } else { s="'"${1:${_ss}}"'"; } + } +$0 !~ /^[ ]*#/ && (Ign == 0 && $'"${_s}"' ~ s) || (Ign ==1 && tolower($'"${_s}"') ~ s) {print $0;} +' "${_BM_BOOKMARK_FILE}" + +} +search_bookmarks() { + [[ ${__n:=0} -eq 1 ]] && { search "${@}" | sort -t'|' -k2 | readLines; return; } + [[ ${__N:=0} -eq 1 ]] && { search "${@}" | sort -t'|' -k2 -r | readLines; return; } + search "${@}" | readLines +} + +# +# Open first bookmark matching <query> +# + +recorded_picture() { + local _lines="$(search "${@}")" + if [ "${_BM_ASK_BEFORE_OPEN,,}" = 'true' -a ${__O:=0} -eq 0 -a ${__Y:=0} -eq 0 ]; then + if [ $(wc -l <<< "${_lines}") -gt 1 ]; then + read -p"More than one URL found. Open all Pictures ? [Y/N] : " -n1 _answer + [[ "${_answer,,}" != 'y' ]] && die "\nUse -O to force the first URL or refine your search" + fi + fi + local _all='' + echo '' + while read _nl; do + readLines <<< "${_nl}" + if [ "${_BM_OPEN_ALL,,}" = 'true' -o ${__Y:=0} -eq 1 ]; then + _all="${_BM_SCREENSHOT_DIRECTORY}/$(awk -F'|' '{print $1}' <<< "${_nl}").png ${_all}" + else + "${_BM_CMD_IMAGE_OPEN}" "${_BM_SCREENSHOT_DIRECTORY}/$(awk -F'|' '{print $1}' <<< "${_nl}").png" + [[ "${_BM_OPEN_FIRST,,}" = 'true' || ${__O:=0} -eq 1 ]] && exit 0 + fi + done <<< "$(echo -e "${_lines}")" + [[ "${_BM_OPEN_ALL,,}" = 'true' || ${__Y:=0} -eq 1 ]] && "${_BM_CMD_IMAGE_OPEN}" "${_all}" +} + +download_title() { + local _lines="$(search "${@}")" + if [ "${_BM_ASK_BEFORE_OPEN,,}" = 'true' -a ${__O:=0} -eq 0 -a ${__Y:=0} -eq 0 ]; then + if [ $(wc -l <<< "${_lines}") -gt 1 ]; then + read -p"More than one URL found. Download Title for all URLs ? [Y/N] : " -n1 _answer + [[ "${_answer,,}" != 'y' ]] && die "\nUse -O to force the first URL or refine your search" + fi + fi + local _all='' + echo '' + while read _nl; do + readLines <<< "${_nl}" + local u="$(awk -F'|' '{print $4}' <<< "${_nl}")" + if [ "${_BM_OPEN_ALL,,}" = 'true' -o ${__Y:=0} -eq 1 ]; then + echo "${u} --> $( titleDl "${u}" )" + else + echo "${u} --> $( titleDl "${u}" )" + [[ "${_BM_OPEN_FIRST,,}" = 'true' || ${__O:=0} -eq 1 ]] && exit 0 + fi + done <<< "$(echo -e "${_lines}")" +} + +open_bookmark() { + local _lines="$(search "${@}")" + if [ "${_BM_ASK_BEFORE_OPEN,,}" = 'true' -a ${__O:=0} -eq 0 -a ${__Y:=0} -eq 0 ]; then + if [ $(wc -l <<< "${_lines}") -gt 1 ]; then + read -p"More than one URL found. Open all ? [Y/N] : " -n1 _answer + [[ "${_answer,,}" != 'y' ]] && die "\nUse -O to force the first URL or refine your search" + fi + fi + local _all='' + while read _nl; do + readLines <<< "${_nl}" + if [ "${_BM_OPEN_ALL,,}" = 'true' -o ${__Y:=0} -eq 1 ]; then + _all="$(awk -F'|' '{print $4}' <<< "${_nl}") ${_all}" + else + "${_BM_CMD_OPEN}" "$(awk -F'|' '{print $4}' <<< "${_nl}")" + [[ "${_BM_OPEN_FIRST,,}" = 'true' || ${__O:=0} -eq 1 ]] && exit 0 + fi + done <<< "$(echo -e "${_lines}")" + [[ "${_BM_OPEN_ALL,,}" = 'true' || ${__Y:=0} -eq 1 ]] && "${_BM_CMD_OPEN}" "${_all}" +} + +copy_bookmark() { + local _lines="$(search "${@}")" + if [ "${_BM_ASK_BEFORE_OPEN,,}" = 'true' -a ${__X:=0} -eq 0 -a ${__Y:=0} -eq 0 ]; then + if [ $(wc -l <<< "${_lines}") -gt 1 ]; then + read -p"More than one URL found. Copy all ? [Y/N] : " -n1 _answer + [[ "${_answer,,}" != 'y' ]] && die "\nUse -X to force the first URL or refine your search" + fi + fi + local _all='' + while read _nl; do + readLines <<< "${_nl}" + if [ "${_BM_OPEN_ALL,,}" = 'true' -o ${__Y:=0} -eq 1 ]; then + _all="$(awk -F'|' '{print $4}' <<< "${_nl}") ${_all}" + else + awk -F'|' '{print $4}' <<< "${_nl}" | "${_BM_CMD_COPY}" + awk -F'|' '{print $4}' <<< "${_nl}" | "${_BM_CMD_COPY}" -b + [[ "${_BM_OPEN_FIRST,,}" = 'true' || ${__X:=0} -eq 1 ]] && exit 0 + fi + done <<< "$(echo -e "${_lines}")" + [[ "${_BM_OPEN_ALL,,}" = 'true' || ${__Y:=0} -eq 1 ]] && "${_BM_CMD_COPY}" <<< "${_all}" + [[ "${_BM_OPEN_ALL,,}" = 'true' || ${__Y:=0} -eq 1 ]] && "${_BM_CMD_COPY}" -b <<< "${_all}" +} + +delete_bookmark() { + [[ -z "${@//[ ]/}" ]] && die "You MUST give an argument !" + local _lines="$(search "${@}")" + if [ "${_BM_DELETE_ALLOWED,,}" = 'true' ]; then + while read _nl; do readLines <<< "${_nl}"; done <<< "$(echo -e "${_lines}")" + if [ "${_BM_ASK_BEFORE_OPEN,,}" = 'true' -a ${__D:=0} -eq 0 -a ${__Y:=0} -eq 0 ]; then + if [ $(wc -l <<< "${_lines}") -ge 1 ]; then + read -p"You're about to delete entry(ies). Ready ? [Y/N] : " -n1 _answer + [[ "${_answer,,}" != 'y' ]] && die "\nUse -D to force the backup if not configured." + echo '' + fi + fi + backupBm + local _all='' + while read _nl; do + if [ "${_BM_DELETE_TO_FILE,,}" = 'true' -o ${__D:=0} -eq 1 ]; then + echo "${_nl}" >> "${_BM_DELETE_FILE}" + fi + [[ "${_BM_DELETE_PICTURE,,}" = 'true' || ${__p:=0} -eq 1 ]] && rm -f -- "${_BM_SCREENSHOT_DIRECTORY}/${_nl%%|*}.png" + IFS='|' read m d a u T t <<< "${_nl}" + sed -i -e '/^'"${m}.*${u//\//\\/}.*${t//\//\\/}"'$/d' "${_BM_BOOKMARK_FILE}" + done <<< "$(echo -e "${_lines}")" + else + die "You're not allowed to delete entries. Change the _BM_DELETE_ALLOWED to true !" + fi +} + +edit_bookmark() { + echo -e "You're about to open your '${_BM_BOOKMARK_FILE}' file with your \$EDITOR.\n\n/!\ Press any key to continue, or CTRL+C to abort. /!\ " + read -n1 toto + ${EDITOR:=vi} "${_BM_BOOKMARK_FILE}" +} + +# +# Diplay some statistics about the bookmarks +# +stats() { + echo "===== Configuration =====" + echo "Bookmark file : ${_BM_BOOKMARK_FILE}" + echo "Trash file : ${_BM_DELETE_FILE}$([[ ! -e "${_BM_DELETE_FILE}" ]] && echo " (but doesn't exist)")" + echo "Config file : ${_BM_CONFIG_FILE}$([[ ! -e "${_BM_CONFIG_FILE}" ]] && echo " (but doesn't exist)")" + echo "Screenshot directory : ${_BM_SCREENSHOT_DIRECTORY}" + echo "Backup file(s) : ${_BM_BOOKMARK_BACKUP_FILE}*" + for i in "${_BM_BOOKMARK_BACKUP_FILE}"*; do + echo " ${i}" + done + echo -e "\n===== Statistics =====" + readarray -t lines < "${_BM_BOOKMARK_FILE}" + echo "# of Bookmarks : ${#lines[@]}" + echo "# of Duplicate : $(awk -F'|' '$0 !~ /^[ ]*#/ {print $1}' "${_BM_BOOKMARK_FILE}" |sort |uniq -d |wc -l)" + local tags="$(awk -F'|' '$0 !~ /^[ ]*#/ {print $NF}' "${_BM_BOOKMARK_FILE}" | sed -e 's/\([[:space:]]*,[[:space:]]*\)/\n/g')" + + echo "# of tags : $( sort -u <<< "${tags,,}" |wc -l)" + echo "Top 14 tags used :" + local nli=0;local pa=0 + ( + sort <<< "${tags,,}" | uniq -c | sort -nr | while read n t; do + (( nli++ ));[[ ${nli} -gt 7 ]] && nli=1 && echo '' + (( pa++ ));[[ ${pa} -gt 14 ]] && break + echo -n "${t// / }:${n} " +done +echo '' +) | column -t -c 17 +echo "# of Pictures : $(ls -1 "${_BM_SCREENSHOT_DIRECTORY}"/*.png |wc -l) [# of files in ${_BM_SCREENSHOT_DIRECTORY}:$(ls -1 "${_BM_SCREENSHOT_DIRECTORY}"/* |wc -l)]" +echo "All Pictures size : $(du -sh "${_BM_SCREENSHOT_DIRECTORY}" | awk '{print $1}')" +local orphaned="$( cd "${_BM_SCREENSHOT_DIRECTORY}" && for i in *; do [[ -z "$(sed -e '/'"${i%%.*}"'/!d' "${_BM_BOOKMARK_FILE}")" ]] && ((orph++)); done; echo "${orph:=0}" )" +local nopics="$( while read i; do [[ ! -f "${_BM_SCREENSHOT_DIRECTORY}/${i%%|*}.png" ]] && ((nopics++)); done <<< ${lines[@]}; echo "${nopics:=0}" )" +echo "Bookmark Without Pic : ${nopics}" +echo "Orphaned pictures : ${orphaned}" +[[ ${__p:=0} -eq 1 ]] && { echo -e "\n===== Orphaned ====="; echo "List of orphaned pictures :"; cd "${_BM_SCREENSHOT_DIRECTORY}" && for i in *; do [[ -z "$(sed -e '/'"${i%%.*}"'/!d' "${_BM_BOOKMARK_FILE}")" ]] && echo " - ${_BM_SCREENSHOT_DIRECTORY}/${i} [Trashed URL should be: $(awk -F'|' 'BEGIN{l="Unknown"} ($1 ~ /'"${i%%.*}"'/) {l=$4;} END {print l}' "${_BM_DELETE_FILE}")]"; done; }; +} + +# +# Output tags. +# + +list_tags() { + local tags="$(awk -F'|' '$0 !~ /^[ ]*#/ {print $NF}' "${_BM_BOOKMARK_FILE}" | sed -e 's/\([[:space:]]*,[[:space:]]*\)/\n/g')" + local nli=0 + ( + sort <<< "${tags,,}" | uniq -c | sort -nr | while read n t; do + (( nli++ ));[[ ${nli} -gt 7 ]] && nli=1 && echo '' + echo -n "${t// / }:${n} " +done +echo '' +) | column -t -c 17 +} + +# +# Stylesheet +# + +style() { + cat <<EOF +<style type="text/css"> + * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + body { + padding: 50px 0 5px 50px; + background: #1f1f1f url(${_BM_SCREENSHOT_DIRECTORY}/black-Linen.png); + font: 12px "Helvetica Neue", Helvetica, Arial, sans-serif; + } + .bm { + position: relative; + float: left; + margin: 15px; + padding: 1px; + opacity: 1; + border: 15px solid black; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-transition: -webkit-box-shadow 600ms; + -moz-transition: -webkit-box-shadow 600ms; + -webkit-box-shadow: 0 0 1px 0 #555, 0 0 10px rgba(0,0,0,.5); + -moz-box-shadow: 0 0 1px 0 #555, 0 0 10px rgba(0,0,0,.5); + overflow: hidden; + } + .bm:hover { + -webkit-box-shadow: 0 0 40px #1ab0ff + , 0 0 3px #06bdff + , 0 1px 1px #4ee2ff + , 0 1px 0 #fff; + -moz-box-shadow: 0 0 40px #1ab0ff + , 0 0 3px #06bdff + , 0 1px 1px #4ee2ff + , 0 1px 0 #fff; + } + .bm:hover img { + opacity: 1; + } + .bm img { + width: 200px; + height: 150px; + opacity: .5; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-transition: opacity 200ms; + -moz-transition: opacity 200ms; + } + .bm p { + margin: 0; + padding: 10px; + width: 100%; + background: rgba(0,0,0,.75); + position: absolute; + left: 0; + color: white; + letter-spacing: 1px; + bottom: -50px; + -webkit-font-smoothing: antialiased; + -webkit-transition: bottom 200ms ease-in; + } + .bm:hover p { + bottom: 0; + } +</style> +EOF +} + +# +# Generate bookmark screenshots +# + +screenshot_take() { + if [ "${_BM_SCREENSHOT_GET,,}" = 'true' -o ${__p:=0} -eq 1 ]; then + [[ ! -d "${_BM_SCREENSHOT_DIRECTORY}" ]] && { mkdir -p "${_BM_SCREENSHOT_DIRECTORY}" || die "Can't create thumbnail directory." ; } + [[ ! -z "${_BM_CMD_PRE_CAPTURE}" ]] && { local _pre="${_BM_CMD_PRE_CAPTURE//\{FILE\}/${1}}"; _pre="${_pre//\{URL\}/${2}}"; ${_pre} ; } + local _cmd="${_BM_CMD_CAPTURE//\{FILE\}/${1}}"; _cmd="${_cmd//\{URL\}/${2}}"; + local _args="${_BM_CMD_CAPTURE_ARGS//\{FILE\}/${1}}"; _args="${_args//\{URL\}/${2}}"; + eval ${_cmd} ${_args} + [[ ! -z "${_BM_CMD_POST_CAPTURE}" ]] && { local _post="${_BM_CMD_POST_CAPTURE//\{FILE\}/${1}}"; _post="${_post//\{URL\}/${2}}"; ${_post} ; } + fi +} + +picturize() { + if [ -z "${1:-}" ]; then + awk -F'|' '$0 !~ /^[ ]*#/ {print $1,$4;}' "${_BM_BOOKMARK_FILE}" | while read m u; do + [[ ! -f "${_BM_SCREENSHOT_DIRECTORY}/${m}.png" || ${__F:=0} -eq 1 ]] && { screenshot_take "${_BM_SCREENSHOT_DIRECTORY}/${m}.png" "${u}" & } + [[ "${_BM_SCREENSHOT_WAIT,,}" = 'true' ]] && wait + done +else + local _lines="$(search "${@}")" + if [ "${_BM_ASK_BEFORE_OPEN,,}" = 'true' -a ${__O:=0} -eq 0 -a ${__Y:=0} -eq 0 ]; then + if [ $(wc -l <<< "${_lines}") -gt 1 ]; then + read -p"More than one URL found. Open all ? [Y/N] : " -n1 _answer + [[ "${_answer,,}" != 'y' ]] && die "\nUse -O to force the first URL or refine your search" + fi + fi + local _all='' + while read _nl; do + readLines <<< "${_nl}" + if [ "${_BM_OPEN_ALL,,}" = 'true' -o ${__Y:=0} -eq 1 ]; then + IFS='|' read m d a u b <<< "${_nl}" + screenshot_take "${_BM_SCREENSHOT_DIRECTORY}/${m}.png" "${u}" & + else + IFS='|' read m d a u b <<< "${_nl}" + screenshot_take "${_BM_SCREENSHOT_DIRECTORY}/${m}.png" "${u}" & + [[ "${_BM_OPEN_FIRST,,}" = 'true' || ${__O:=0} -eq 1 ]] && exit 0 + fi + done <<< "$(echo -e "${_lines}")" + fi +} + +bookmark_generator() { + [[ ! -f "${_BM_SCREENSHOT_DIRECTORY}/black-Linen.png" && -f "/usr/share/bm/black-Linen.png" ]] && cp "/usr/share/bm/black-Linen.png" "${_BM_SCREENSHOT_DIRECTORY}/black-Linen.png" + echo "<!DOCTYPE html><html><head><meta charset="UTF-8"><title>bm v${VERSION} : all your bookmarks" > "${1:-${_BM_HTML_FILE}}" + style >> "${1:-${_BM_HTML_FILE}}" + echo "" >> "${1:-${_BM_HTML_FILE}}" + # search "${2:-}" | while IFS='|' read m a u T t d; do + awk '$0 !~ /^[ ]*#/' "${_BM_BOOKMARK_FILE}" | while IFS='|' read m d a u T t; do + echo "
+ ${u} +

${T}
View image

+
" >> "${1:-${_BM_HTML_FILE}}" +done +echo "" >> "${1:-${_BM_HTML_FILE}}" +[[ ${__O:=0} -eq 1 ]] && "${_BM_CMD_OPEN}" "${1:-${_BM_HTML_FILE}}" +} + +# +# die print message to stderr and exit +# + +die() { + echo -e "${@}" >&2 + exit 1 +} + + +# +# checkBinaries check the script is able to run and give hints +# + +checkBinaries() { + if [ ${#} -ne 0 ]; then + local cmdErr="${@}" + else + local cmdErr="sed awk date iconv cat curl ${_BM_CMD_OPEN} ${_BM_CMD_MD5} column" + fi + set ${cmdErr} + while [ ${#} -ne 0 ]; do + if ! command -v "${1:-}" &>/dev/null; then + echo "command not found: ${1}" >&2 + local rc=1 + fi + shift + done + [[ ${rc:=0} -ne 0 ]] && die "At least one command is missing. Please install it before using bm." + # Checking sed + local rc=1 + [[ ! -f "/tmp/sedtest.$$" ]] && \ + echo -n 'toto' > "/tmp/sedtest.$$" && \ + sed -i.bak -e 's;^toto$;tata;' "/tmp/sedtest.$$" && \ + [[ -f "/tmp/sedtest.$$.bak" ]] && \ + grep -q "tata" "/tmp/sedtest.$$" && \ + rc=0 && \ + rm -f "/tmp/sedtest.$$" "/tmp/sedtest.$$.bak" + [[ ${rc} -ne 0 ]] && die "sed seems to not handle -i argument properly, please check" + } + +# +# defineColors generate the variables to use to colorize the output +# + +defineColors() { + export BLACK="\e[30m"; export BLACK_LIGHT="\e[90m"; export GRAY_DARK="${BLACK_LIGHT}" + export RED="\e[31m"; export RED_LIGHT="\e[91m" + export GREEN="\e[32m"; export GREEN_LIGHT="\e[92m" + export YELLOW="\e[33m"; export YELLOW_LIGHT="\e[93m" + export BLUE="\e[34m"; export BLUE_LIGHT="\e[94m" + export MAGENTA="\e[35m"; export MAGENTA_LIGHT="\e[95m" + export CYAN="\e[36m"; export CYAN_LIGHT="\e[96m" + export GRAY="\e[37m"; export GRAY_LIGHT="\e[97m"; export WHITE="${GRAY_LIGHT}" + + export RESET="\e[0m" + export BOLD="\e[1m"; export BOLD_RESET="\e[21m" + export DIM="\e[2m"; export DIM_RESET="\e[22m" + export UNDERLINE="\e[4m"; export UNDERLINE_RESET="\e[24m" + export INVERT="\e[7m"; export INVERT_RESET="\e[27m" + if [ ! -z "${1:-}" ]; then + echo "Use the following colors to fit your needs :" + ( + echo -e "${RESET}${BLACK}BLACK${RESET} - ${BLACK_LIGHT}BLACK_LIGHT${RESET} - ${BOLD}${BLACK}BOLD BLACK${RESET} - ${DIM}${BLACK}DIM BLACK${RESET}" + echo -e "${RESET}${RED}RED${RESET} - ${RED_LIGHT}RED_LIGHT${RESET} - ${BOLD}${RED}BOLD RED${RESET} - ${DIM}${RED}DIM RED${RESET}" + echo -e "${RESET}${GREEN}GREEN${RESET} - ${GREEN_LIGHT}GREEN_LIGHT${RESET} - ${BOLD}${GREEN}BOLD GREEN${RESET} - ${DIM}${GREEN}DIM GREEN${RESET}" + echo -e "${RESET}${YELLOW}YELLOW${RESET} - ${YELLOW_LIGHT}YELLOW_LIGHT${RESET} - ${BOLD}${YELLOW}BOLD YELLOW${RESET} - ${DIM}${YELLOW}DIM YELLOW${RESET}" + echo -e "${RESET}${BLUE}BLUE${RESET} - ${BLUE_LIGHT}BLUE_LIGHT${RESET} - ${BOLD}${BLUE}BOLD BLUE${RESET} - ${DIM}${BLUE}DIM BLUE${RESET}" + echo -e "${RESET}${MAGENTA}MAGENTA${RESET} - ${MAGENTA_LIGHT}MAGENTA_LIGHT${RESET} - ${BOLD}${MAGENTA}BOLD MAGENTA${RESET} - ${DIM}${MAGENTA}DIM MAGENTA${RESET}" + echo -e "${RESET}${CYAN}CYAN${RESET} - ${CYAN_LIGHT}CYAN_LIGHT${RESET} - ${BOLD}${CYAN}BOLD CYAN${RESET} - ${DIM}${CYAN}DIM CYAN${RESET}" + echo -e "${RESET}${GRAY}GRAY${RESET} - ${GRAY_LIGHT}GRAY_LIGHT${RESET} - ${BOLD}${GRAY}BOLD GRAY${RESET} - ${DIM}${GRAY}DIM GRAY${RESET}" + echo -e "${RESET}${GRAY_DARK}GRAY_DARK${RESET} - ${WHITE}WHITE${RESET}" + ) | column -t + echo -e "You could also use the ${UNDERLINE}UNDERLINE, the ${INVERT}INVERT and the ${RESET}RESET variable. INVERT_RESET and UNDERLINE_RESET also available" + exit 0 + fi +} + +############################################################################## +# MAIN +############################################################################## + +##################### TRANSITIONNAL ############################ +_regex='^(help|version|tags|ls|list|search|open|add|view|stats|statistics|clear|colors)$' +if [[ ${1:-} =~ ${_regex} ]]; then + case "$1" in + tags) __L=1 ;; + ls|list) __l=1 ;; + search) __s=1; __search="${@:2}" ;; + open) __o=1; __open="${@:2}";; + add) __a=1; __url="${2:-}"; __T="${3:-}"; __t="${4:-}"; __A="${5:-}";; + view) __r=1; __search="${@:2}" ;; + stats|statistics) __S=1 ;; + clear) die "This now a deprecated feature. Use -d instead." ;; + colors) __C=1;; + help) usage 1;exit 0;; + version) echo -n "${0} v${VERSION}";exit 0;; + esac +else + # -c config file + # -C colors print ==> exit + # -b bookmark file + # -d delete + # -D deletefile if set override _BM_DELETE_TO_FILE to true + # -w working dir + # -a add url =/= -l, -s, -S, -O, -o, -x, -X, -g, -C + # -A Accelerator + # -T title + # -t tags + # -s search + # -S stats + # -l list all url + # -o open + # -O open first + # -x copy to clipboard + # -X copy first URL + # -h help + # -H more help + # -v version + # -V more version ? + # -g generate html file + # -G use this file + # -g -g generate 1 file per tags + # used : AaBbCcDdE-F-GgHh-i----Ll--NnOoPp-q-rSsTt--Vv-wXxY--z---------- + # available : ---------e-f----I-JjKk--Mm------Q-R-----Uu--W----yZ-0123456789 + while getopts ":a:A:b:c:d:G:o:P:q:r:s:t:T:w:x:BCDEFghHilLnNOpPSvVXYz" option; do + case ${option} in + a) __a=1; __url="${OPTARG}";; # Add url to bookmark + A) __A="${OPTARG}";; # AcceleratoR + b) __b="${OPTARG}";; # Bookmark file + B) __B=1;; # Don't load the default config file + c) __c="${OPTARG}";; # Config file + C) __C=1;; # Print color table + d) __d=1; __del="${OPTARG}";; # Config file + D) __D=1;; # Print color table + E) __E=1;; # Open the bm.bm file with the $EDITOR + F) __F=1;; # Force the add or delete or picture + g) (( __g++ ));; # generate html file(s) + G) __G=1; __file="${OPTARG}";; # Generate this file (only for g=1) + h|H) usage; exit 0;; # Help + i) __i=1;; # Ignore case when searching + l) __l=1;; # List all bookmarks + L) __L=1;; # List all tags + n) __n=1;; # sort by date + N) __N=1;; # sort by date (reverse) + o) __o=1; __open="${OPTARG}";; # Open + O) __O=1;; # Open First + p) __p=1;; # Take a screenshot + P) __P=1; __search="${OPTARG}";; # Take all screenshot + q) __q=1; __search="${OPTARG}";; # Search + r) __r=1; __search="${OPTARG}";; # Search + s) __s=1; __search="${OPTARG}";; # Search + S) __S=1;; # Print Statistics + t) __t="${OPTARG}";; # tags for a URL + T) __T="${OPTARG}";; # Title for a URL + v|V) echo -n "${0} v${VERSION}"; [[ "${option}" = 'V' ]] && echo -n " [commit: ${RELEASE}]"; echo ''; exit 0;; + w) __w="${OPTARG}";; # Working directory + x) __x=1; __copy="${OPTARG}";; # Copy + X) __X=1;; # Copy First + Y) __Y=1;; # Open/copy All + z) __z=1;; # Alternative print listing + :) echo "Missing argument for '-${OPTARG}'" >&2 ; exit 1 ;; + ?) echo "Argument unknown '-${OPTARG}'" >&2 ; exit 1 ;; # usage;; + *) echo "Argument unknown '-${option}'" >&2 ; exit 1 ;; # usage;; + esac + done +fi + +# Parsing args is done, Starting to work, so check if binaries are here +checkBinaries + +# defineColors MUST be called before the config() or _BM_PRINT_LINE will be in trouble. +defineColors +# Before loading config, checking if working dir exist... +if [ ! -z "${__w:=}" -a ! -d "${__w}" ]; then + die "Working directory '${__w}' doesn't exist !" >&2 +fi +# Loading config +config "${__c}" +[[ ! -z "${__b:=}" ]] && export _BM_BOOKMARK_FILE="${__b}" + +# Following config, we maybe not have to check the capture tool +[[ ${_BM_CMD_CAPTURE_CHECK} ]] && checkBinaries "${_BM_CMD_CAPTURE}" + +# If no bookmark file exist, create one if allowed +if [ ! -f "${_BM_BOOKMARK_FILE}" -a "${_BM_CREATE_BOOKMARK_FILE,,}" = 'true' ]; then + # Remember fields : 1=md5, 2=date, 2=accel, 3=url, 4=title, 5=tags + echo "eef521de8df447ad392dbace16cf2edc|$(date '+%FT%TZ')|:bm|https://github.com/The-Repo-Club/repomenu-extra/|Download link for repomenu-extra|default,shell,scripts" >> "${_BM_BOOKMARK_FILE}" +fi + +# Starting to work with args. If none probably list... +[[ ${#} -eq 0 && "${_BM_NO_ARGS_FORCE_HELP,,}" = 'true' ]] && { usage ; exit; } + +# Only one action at a time +if [ $(( ${__a:=0} + ${__C:=0} + ${__d:=0} + ${__E:=0} + ${__g:=0} + ${__l:=0} + ${__L:=0} + ${__o:=0} + ${__P:=0} + ${__q:=0} + ${__r:=0} + ${__s:=0} + ${__S:=0} + ${__x:=0} )) -gt 1 ]; then + echo "You have to choose between -a, -C, -d, -E, -g, -l, -L, -o, -P, -q, -r, -s, -S, -x" >&2 + echo "Use -h for help" >&2 + exit 0 +fi +if [ $(( ${__O:=0} + ${__Y:=0} + ${__X:=0} )) -gt 1 ]; then + echo "You have to choose between -O, -X, -Y" >&2 + echo "Use -h for help" >&2 + exit 0 +fi + +# Executing actions +[[ ${__a} -eq 1 ]] && { saveUrl ; exit ; } +[[ ${__C} -eq 1 ]] && { defineColors 1 ; exit ; } +[[ ${__d} -eq 1 ]] && { delete_bookmark "${__del}"; exit ; } +[[ ${__g} -ge 1 ]] && { bookmark_generator "${__file:=}" ''; exit ; } +[[ ${__l} -eq 1 ]] && { search ; exit; } +[[ ${__L} -eq 1 ]] && { list_tags ; exit; } +[[ ${__o} -eq 1 ]] && { open_bookmark "${__open}" ; exit; } +[[ ${__P} -eq 1 ]] && { picturize "${__search}" ; exit; } +[[ ${__r} -eq 1 ]] && { recorded_picture "${__search}" ; exit; } +[[ ${__q} -eq 1 ]] && { download_title "${__search}" ; exit; } +[[ ${__s} -eq 1 ]] && { search_bookmarks "${__search}" ; exit; } +[[ ${__S} -eq 1 ]] && { stats ; exit; } +[[ ${__x} -eq 1 ]] && { copy_bookmark "${__copy}" ; exit; } +[[ ${__E} -eq 1 ]] && { edit_bookmark ; exit; } +search + +# $Format:%cn @ %cD$ : $Id$ diff --git a/.local/bin/rofi/bm_add b/.local/bin/rofi/bm_add new file mode 100755 index 000000000..e0743e644 --- /dev/null +++ b/.local/bin/rofi/bm_add @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# bm_add +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Fri 09 December 2022, 06:43:52 AM [GMT] +#Modified: +# Fri 09 December 2022, 08:03:21 AM [GMT] +# +#Description: +# +# +#Dependencies: +# bm +# + +result() { + echo -n | rofi -dmenu -p "${1:-Add a bookmark:...}" -mesg "+ Add a Bookmark" +} + +url="$(result "URL:")" + +if [ -z "$url" ]; then + exit +fi + +title="$(result "Title:")" +tags="$(result "Tags (comma delimited):")" + +bm -w "$HOME/.config/rofi/bookmarks/" -a "$url" -T "$title" -t "$tags" diff --git a/.local/bin/rofi/bm_remove b/.local/bin/rofi/bm_remove new file mode 100755 index 000000000..e48991671 --- /dev/null +++ b/.local/bin/rofi/bm_remove @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# bm_remove +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Fri 09 December 2022, 06:43:41 AM [GMT] +#Modified: +# Fri 14 July 2023, 11:04:29 PM [GMT+1] +# +#Description: +# +# +#Dependencies: +# bm +# +# shellcheck disable=all + +bmFile="$HOME/.config/rofi/bookmarks/bm.bm" + +if [[ ! -f $bmFile ]]; then + printf "%s\n" "No current bookmark file found."; + exit +fi + +declare -A bmarray; + +while IFS=\| read -r md5 date accel url title tags; +do + bookmark="$title-$url-$tags"; + bmarray["$bookmark"]="$url"; +done < "$bmFile" + +function load() { + while IFS=\| read -r md5 date accel url title tags; + do + bookmark="$title-$url-$tags"; + printf "%s\n" "$bookmark"; + done < "$bmFile" + +} + +choice=$(load | rofi -dmenu i -p "${1:-Remove a bookmark:...}" -mesg "- Remove a Bookmark") + +LOOPSETTING="true" +while [ -n "$LOOPSETTING" ]; do + [ -n "$choice" ] || exit + unset LOOPSETTING + case "$choice" in + *) bm -w "$HOME/.config/rofi/bookmarks/" -d "${bmarray[$choice]}" -D;; + esac +done diff --git a/.local/bin/rofi/bm_viewer b/.local/bin/rofi/bm_viewer new file mode 100755 index 000000000..110d00894 --- /dev/null +++ b/.local/bin/rofi/bm_viewer @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# bm_viewer +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Fri 09 December 2022, 06:43:30 AM [GMT] +#Modified: +# Fri 14 July 2023, 11:04:22 PM [GMT+1] +# +#Description: +# +# +#Dependencies: +# bm +# +# shellcheck disable=all + +bmFile="$HOME/.config/rofi/bookmarks/bm.bm" + +if [[ ! -f $bmFile ]]; then + printf "%s\n" "No current bookmark file found."; + exit +fi + +declare -A bmarray; + +function load() { + printf "Add Bookmark\nRemove Bookmark\n" + while IFS=\| read -r md5 date accel url title tags; + do + bookmark="$title-$url-$tags"; + printf "%s\n" "$title"; + done < "$bmFile" +} + +choice=$(load | rofi -dmenu -p "${1:-Select a bookmark:...}" -mesg "+ Add Bookmark - Remove Bookmark Select Bookmarks") + +LOOPSETTING="true" +while [ -n "$LOOPSETTING" ]; do + [ -n "$choice" ] || exit + unset LOOPSETTING + case "$choice" in + Add*) bm_add ;; + Remove*) bm_remove ;; + *) bm -w "$HOME/.config/rofi/bookmarks/" -o "${bmarray[$choice]}";; + esac +done diff --git a/.local/bin/rofi/clipmenu_run b/.local/bin/rofi/clipmenu_run new file mode 100755 index 000000000..f2f8a8401 --- /dev/null +++ b/.local/bin/rofi/clipmenu_run @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# clipmenu_run +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 07 December 2022, 11:01:52 PM [GMT] +#Modified: +# Sun 06 August 2023, 11:39:26 AM [GMT+1] +# +#Description: +# +# +#Dependencies: +# +# +# shellcheck disable=all + +clipmenu -p ClipMenu -mesg "Copy from clipboard." \ No newline at end of file diff --git a/.local/bin/rofi/menu b/.local/bin/rofi/menu new file mode 100755 index 000000000..3f977c75b --- /dev/null +++ b/.local/bin/rofi/menu @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# menu +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 10 March 2021, 12:34:47 PM [GMT+1] +#Modified: +# Thu 08 December 2022, 07:37:35 AM [GMT] +# +#Description: +# +# +#Dependencies: +# rofi +# +# shellcheck disable=all + +programs=$(compgen -c | sort -u | tail -n +9) +cmd=$(echo -e "$programs" | rofi -dmenu -p "Programs" -mesg "Select a program.") +exec $cmd \ No newline at end of file diff --git a/.local/bin/rofi/music b/.local/bin/rofi/music new file mode 100755 index 000000000..1e1bd023d --- /dev/null +++ b/.local/bin/rofi/music @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# -*-coding:utf-8 -*- +# Auto updated? +# Yes +#File : +# music +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +# Created: +# Wed 10 March 2021, 12:34:47 PM [GMT] +# Modified: +# Sat 05 August 2023, 11:57:57 PM [GMT+1] +# +# Description: +# +# +# shellcheck disable=all + +MUSICPLAYER="mplayer" +CURRENT_SONG=$(lsof -c "$MUSICPLAYER" | grep -F ".mp3" | awk -F"/" '{ print $NF; }' | cut -d'.' -f1) +PLAYING_MSG="Currently Playing :: ${CURRENT_SONG}" +MUSICDIR=$HOME/Music + +for Song in "$MUSICDIR/"*; do + if [ -f "$Song" ]; then + Name=${Song##*/} + case $Name in + *.mp3 | *.flac | *.wav | .ogg) + options+=${Song##*/}$'\n' + ;; + esac + fi +done +CHOICE=$(rofi -dmenu -no-custom -p "Muisc Player" -kb-custom-1 "Alt+Return" -mesg "${PLAYING_MSG}" "$@" </dev/null; then + killall -9 "$MUSICPLAYER" + fi + if [[ $MUSICPLAYER == "mplayer" ]]; then + $MUSICPLAYER -ao alsa "$MUSICDIR/$CHOICE" >> /dev/null & + else + $MUSICPLAYER "$MUSICDIR/$CHOICE" & + fi + ;; + *) + echo "Program terminated." && exit + ;; + esac +elif [[ $ret -gt 1 ]]; then + if [[ $ret -eq 10 ]]; then + killall -9 "$MUSICPLAYER" + fi +fi \ No newline at end of file diff --git a/.local/bin/rofi/passmenu b/.local/bin/rofi/passmenu new file mode 100755 index 000000000..c58ac010c --- /dev/null +++ b/.local/bin/rofi/passmenu @@ -0,0 +1,897 @@ +#!/usr/bin/env bash + +# passmenu +# (c) 2022 Wayne Wesley +basecommand="$0" + +# set default settings +_rofi () { + rofi -no-auto-select -i "$@" +} + +_pwgen () { + pwgen -y "$@" +} + +_image_viewer () { + feh - +} + +_clip_in_primary() { + xclip +} + +_clip_in_clipboard() { + xclip -selection clipboard +} + +_clip_out_primary() { + xclip -o +} + +_clip_out_clipboard() { + xclip --selection clipboard -o +} + + +config_dir=${XDG_CONFIG_HOME:-$HOME/.config} +cache_dir=${XDG_CACHE_HOME:-$HOME/.cache} + +# We expect to find these fields in pass(1)'s output +URL_field='url' +USERNAME_field='user' +AUTOTYPE_field='autotype' +OTPmethod_field='otp_method' + +default_autotype="user :tab pass" +delay=2 +wait=0.2 +xdotool_delay=12 +default_do='menu' # menu, copyPass, typeUser, typePass, copyUser, copyUrl, viewEntry, typeMenu, actionMenu, copyMenu, openUrl +auto_enter='false' +notify='false' +help_color="" +clip=primary +clip_clear=45 +default_user="${ROFI_PASS_DEFAULT_USER-$(whoami)}" +default_user2=john_doe +password_length=12 +fix_layout=false + +# default shortcuts +autotype="Alt+1" +type_user="Alt+2" +type_pass="Alt+3" +open_url="Alt+4" +copy_name="Alt+u" +copy_url="Alt+l" +copy_pass="Alt+p" +show="Alt+o" +copy_menu="Alt+c" +action_menu="Alt+a" +type_menu="Alt+t" +help="Alt+h" +switch="Alt+x" +insert_pass="Alt+n" +qrcode="Alt+q" +previous_root="Shift+Left" +next_root="Shift+Right" + +# Safe permissions +umask 077 + +has_qrencode() { + command -v qrencode >/dev/null 2>&1 +} + +listgpg () { + mapfile -d '' pw_list < <(find -L . -name '*.gpg' -print0) + pw_list=("${pw_list[@]#./}") + printf '%s\n' "${pw_list[@]}" | sort -n +} + +# get all password files and output as newline-delimited text +list_passwords() { + cd "${root}" || exit + mapfile -t pw_list < <(listgpg) + printf '%s\n' "${pw_list[@]%.gpg}" | sort -n +} + +doClip () { + case "$clip" in + "primary") _clip_in_primary ;; + "clipboard") _clip_in_clipboard ;; + "both") _clip_in_primary; _clip_out_primary | _clip_in_clipboard;; + esac +} + +checkIfPass () { + printf '%s\n' "${root}: $selected_password" >| "$cache_dir/passmenu/last_used" +} + + +autopass () { + x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}') + xset r off + + rm -f "$cache_dir/passmenu/last_used" + printf '%s\n' "${root}: $selected_password" > "$cache_dir/passmenu/last_used" + for word in ${stuff["$AUTOTYPE_field"]}; do + case "$word" in + ":tab") xdotool key Tab;; + ":space") xdotool key space;; + ":delay") sleep "${delay}";; + ":enter") xdotool key Return;; + ":otp") printf '%s' "$(generateOTP)" | xdotool type --delay ${xdotool_delay} --clearmodifiers --file -;; + "pass") printf '%s' "${password}" | xdotool type --delay ${xdotool_delay} --clearmodifiers --file -;; + "path") printf '%s' "${selected_password}" | rev | cut -d'/' -f1 | rev | xdotool type --clearmodifiers --file -;; + *) printf '%s' "${stuff[${word}]}" | xdotool type --delay ${xdotool_delay} --clearmodifiers --file -;; + esac + done + + if [[ ${auto_enter} == "true" ]]; then + xdotool key Return + fi + + xset r "$x_repeat_enabled" + unset x_repeat_enabled + clearUp +} + +generateQrCode() { + has_qrencode + + if [[ $? -eq "1" ]]; then + printf '%s\n' "qrencode not found" | _rofi -dmenu + exit_code=$? + if [[ $exit_code -eq "1" ]]; then + exit + else + "${basecommand}" + fi + fi + + checkIfPass + pass "$selected_password" | head -n 1 | qrencode -d 300 -v 8 -l H -o - | _image_viewer + if [[ $? -eq "1" ]]; then + printf '%s\n' "" | _rofi -dmenu -mesg "Image viewer not defined or cannot read from pipe" + exit_value=$? + if [[ $exit_value -eq "1" ]]; then + exit + else + "${basecommand}" + fi + fi + clearUp +} + +openURL () { + checkIfPass + $BROWSER "$(PASSWORD_STORE_DIR="${root}" pass "$selected_password" | grep "${URL_field}: " | gawk '{sub(/:/,"")}{print $2}1' | head -1)"; exit; + clearUp +} + +typeUser () { + checkIfPass + + x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}') + xset r off + + printf '%s' "${stuff[${USERNAME_field}]}" | xdotool type --delay ${xdotool_delay} --clearmodifiers --file - + + xset r "$x_repeat_enabled" + unset x_repeat_enabled + + clearUp +} + +typePass () { + checkIfPass + + x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}') + xset r off + + printf '%s' "${password}" | xdotool type --delay ${xdotool_delay} --clearmodifiers --file - + + if [[ $notify == "true" ]]; then + if [[ "${stuff[notify]}" == "false" ]]; then + : + else + notify-send "passmenu" "finished typing password"; + fi + elif [[ $notify == "false" ]]; then + if [[ "${stuff[notify]}" == "true" ]]; then + notify-send "passmenu" "finished typing password"; + else + : + fi + fi + + xset r "$x_repeat_enabled" + unset x_repeat_enabled + clearUp +} + +typeField () { + checkIfPass + local to_type + + x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}') + xset r off + + case $typefield in + "OTP") to_type="$(generateOTP)" ;; + *) to_type="${stuff[${typefield}]}" ;; + esac + + printf '%s' "$to_type" | xdotool type --delay ${xdotool_delay} --clearmodifiers --file - + + xset r "$x_repeat_enabled" + unset x_repeat_enabled + unset to_type + + clearUp +} + +generateOTP () { + checkIfPass + + # First, we check if there is a non-conventional OTP command in the pass file + if PASSWORD_STORE_DIR="${root}" pass "$selected_password" | grep -q "${OTPmethod_field}: "; then + # We execute the commands after otp_method: AS-IS + bash -c "$(PASSWORD_STORE_DIR="${root}" pass "$selected_password" | grep "${OTPmethod_field}: " | cut -d' ' -f2-)" + else + # If there is no method defined, fallback to pass-otp + PASSWORD_STORE_DIR="${root}" pass otp "$selected_password" + fi + + clearUp +} + +copyUser () { + checkIfPass + printf '%s' "${stuff[${USERNAME_field}]}" | doClip + clearUp +} + +copyField () { + checkIfPass + printf '%s' "${stuff[${copyfield}]}" | doClip + clearUp +} + +copyURL () { + checkIfPass + printf '%s' "${stuff[${URL_field}]}" | doClip + clearUp +} + +copyPass () { + checkIfPass + printf '%s' "$password" | doClip + if [[ $notify == "true" ]]; then + notify-send "passmenu" "Copied Password\\nClearing in $clip_clear seconds" + fi + + if [[ $notify == "true" ]]; then + (sleep $clip_clear; printf '%s' "" | _clip_in_primary; printf '%s' "" | _clip_in_clipboard | notify-send "passmenu" "Clipboard cleared") & + elif [[ $notify == "false" ]]; then + (sleep $clip_clear; printf '%s' "" | _clip_in_primary; printf '%s' "" | _clip_in_clipboard) & + fi +} + +viewEntry () { + checkIfPass + showEntry "${selected_password}" +} + +generatePass () { + askmenu_content=( + "Yes" + "No" + ) + + askGenMenu=$(printf '%s\n' "${askmenu_content[@]}" | _rofi -dmenu -p "Generate new Password for ${selected_password}? > ") + askgen_exit=$? + + if [[ $askgen_exit -eq 1 ]]; then + exit + fi + if [[ $askGenMenu == "Yes" ]]; then + true + elif [[ $askGenMenu == "No" ]]; then + actionMenu + fi + + checkIfPass + + symbols_content=( + "0 Cancel" + "1 Yes" + "2 No" + ) + + symbols=$(printf '%s\n' "${symbols_content[@]}" | _rofi -dmenu -p "Use Symbols? > ") + symbols_val=$? + + if [[ $symbols_val -eq 1 ]]; then + exit + fi + if [[ $symbols == "0 Cancel" ]]; then + mainMenu; + elif [[ $symbols == "1 Yes" ]]; then + symbols=""; + elif [[ $symbols == "2 No" ]]; then + symbols="-n"; + fi + + HELP="Enter Number or hit Enter to use default length" + length=$(printf '%s' "" | _rofi -dmenu -mesg "${HELP}" -p "Password length? (Default: ${password_length}) > ") + length_exit=$? + + if [[ $length_exit -eq 1 ]]; then + exit + fi + if [[ $length == "" ]]; then + PASSWORD_STORE_DIR="${root}" pass generate ${symbols} -i "$selected_password" "${password_length}" > /dev/null; + else + PASSWORD_STORE_DIR="${root}" pass generate ${symbols} -i "$selected_password" "${length}" > /dev/null; + fi +} + +# main Menu +mainMenu () { + if [[ $1 == "--bmarks" ]]; then + selected_password="$(list_passwords 2>/dev/null \ + | _rofi -mesg "Bookmarks Mode. ${switch} to switch" \ + -dmenu \ + -kb-custom-10 "${switch}" \ + -select "$entry" \ + -p "passmenu > ")" + + rofi_exit=$? + + if [[ $rofi_exit -eq 1 ]]; then + exit + elif [[ $rofi_exit -eq 19 ]]; then + ${basecommand} + elif [[ $rofi_exit -eq 0 ]]; then + openURL + fi + else + unset selected_password + + args=( -dmenu + -kb-custom-1 "${autotype}" + -kb-custom-2 "${type_user}" + -kb-custom-3 "${type_pass}" + -kb-custom-4 "${open_url}" + -kb-custom-5 "${copy_name}" + -kb-custom-6 "${copy_pass}" + -kb-custom-7 "${show}" + -kb-custom-8 "${copy_url}" + -kb-custom-9 "${type_menu}" + -kb-custom-10 "${previous_root}" + -kb-custom-11 "${next_root}" + -kb-custom-14 "${action_menu}" + -kb-custom-15 "${copy_menu}" + -kb-custom-16 "${help}" + -kb-custom-17 "${switch}" + -kb-custom-18 "${insert_pass}" + -kb-custom-19 "${qrcode}" + ) + args+=( -kb-mode-previous "" # These keyboard shortcut options are needed, because + -kb-mode-next "" # Shift+ are otherwise taken by rofi. + -select "$entry" + -mesg "PW Store: ${root}" + -p "passmenu > " ) + + selected_password="$(list_passwords 2>/dev/null | _rofi "${args[@]}")" + + rofi_exit=$? + if [[ $rofi_exit -eq 1 ]]; then + exit + fi + + # Actions based on exit code, which do not need the entry. + # The exit code for -kb-custom-X is X+9. + case "${rofi_exit}" in + 19) roots_index=$(( (roots_index-1+roots_length) % roots_length)); root=${roots[$roots_index]}; mainMenu; return;; + 20) roots_index=$(( (roots_index+1) % roots_length)); root=${roots[$roots_index]}; mainMenu; return;; + 25) helpMenu; return;; + 26) ${basecommand} --bmarks; return;; + esac + + mapfile -t password_temp < <(PASSWORD_STORE_DIR="${root}" pass show "$selected_password") + password=${password_temp[0]} + + if [[ ${password} == "#FILE="* ]]; then + pass_file="${password#*=}" + mapfile -t password_temp2 < <(PASSWORD_STORE_DIR="${root}" pass show "${pass_file}") + password=${password_temp2[0]} + fi + + fields=$(printf '%s\n' "${password_temp[@]:1}" | awk '$1 ~ /:$/ || /otpauth:\/\// {$1=$1;print}') + declare -A stuff + stuff["pass"]=${password} + + if [[ -n $fields ]]; then + while read -r LINE; do + unset _id _val + case "$LINE" in + "otpauth://"*|"${OTPmethod_field}"*) + _id="OTP" + _val="" + ;; + *) + _id="${LINE%%: *}" + _val="${LINE#* }" + ;; + esac + + if [[ -n "$_id" ]]; then + stuff["${_id}"]=${_val} + fi + done < <(printf '%s\n' "${fields}") + + if test "${stuff['autotype']+autotype}"; then + : + else + stuff["autotype"]="${USERNAME_field} :tab pass" + fi + fi + fi + + if [[ -z "${stuff["${AUTOTYPE_field}"]}" ]]; then + if [[ -n $default_autotype ]]; then + stuff["${AUTOTYPE_field}"]="${default_autotype}" + fi + fi + if [[ -z "${stuff["${USERNAME_field}"]}" ]]; then + if [[ -n $default_user ]]; then + if [[ "$default_user" == ":filename" ]]; then + stuff["${USERNAME_field}"]="$(basename "$selected_password")" + else + stuff["${USERNAME_field}"]="${default_user}" + fi + fi + fi + pass_content="$(for key in "${!stuff[@]}"; do printf '%s\n' "${key}: ${stuff[$key]}"; done)" + + # actions based on keypresses + # The exit code for -kb-custom-X is X+9. + case "${rofi_exit}" in + 0) typeMenu;; + 10) sleep $wait; autopass;; + 11) sleep $wait; typeUser;; + 12) sleep $wait; typePass;; + 13) openURL;; + 14) copyUser;; + 15) copyPass;; + 16) viewEntry;; + 17) copyURL;; + 18) default_do="menu" typeMenu;; + 23) actionMenu;; + 24) copyMenu;; + 27) insertPass;; + 28) generateQrCode;; + esac + clearUp +} + + +clearUp () { + password='' + selected_password='' + unset stuff + unset password + unset selected_password + unset password_temp + unset stuff +} + +helpMenu () { + _rofi -dmenu -mesg "Hint: All hotkeys are configurable in config file" -p "Help > " <<- EOM + ${autotype}: Autotype + ${type_user}: Type Username + ${type_pass}: Type Password + ${qrcode}: Generate and display qrcode + --- + ${copy_name}: Copy Username + ${copy_pass}: Copy Password + ${copy_url}: Copy URL + ${open_url}: Open URL + ${copy_menu}: Copy Custom Field + --- + ${action_menu}: Edit, Move, Delete, Re-generate Submenu + ${show}: Show Password File + ${insert_pass}: Insert new Pass Entry + ${switch}: Switch Pass/Bookmark Mode + --- + ${previous_root}: Switch to previous password store (--root) + ${next_root}: Switch to next password store (--root) +EOM +help_val=$? + +if [[ $help_val -eq 1 ]]; then + exit; +else + unset helptext; mainMenu; +fi +} + + +typeMenu () { + if [[ -n $default_do ]]; then + if [[ $default_do == "menu" ]]; then + checkIfPass + local -a keys=("${!stuff[@]}") + keys=("${keys[@]/$AUTOTYPE_field}") + typefield=$({ printf '%s' "${AUTOTYPE_field}" ; printf '%s\n' "${keys[@]}" | sort; } | _rofi -dmenu -p "Choose Field to type > ") + typefield_exit=$? + if [[ $typefield_exit -eq 1 ]]; then + exit + fi + case "$typefield" in + '') exit;; + 'pass') sleep $wait; typePass;; + "${AUTOTYPE_field}") sleep $wait; autopass;; + *) sleep $wait; typeField + esac + clearUp + elif [[ $default_do == "${AUTOTYPE_field}" ]]; then + sleep $wait; autopass + else + ${default_do} + fi + fi +} + +copyMenu () { + checkIfPass + copyfield=$(printf '%s\n' "${!stuff[@]}" | sort | _rofi -dmenu -p "Choose Field to copy > ") + val=$? + if [[ $val -eq 1 ]]; then + exit; + fi + if [[ $copyfield == "pass" ]]; then + copyPass; + else + copyField + fi + clearUp +} + +actionMenu () { + checkIfPass + action_content=("< Return" + "---" + "1 Move Password File" + "2 Copy Password File" + "3 Delete Password File" + "4 Edit Password File" + "5 Generate New Password" + ) + + action=$(printf '%s\n' "${action_content[@]}" | _rofi -dmenu -p "Choose Action > ") + if [[ ${action} == "1 Move Password File" ]]; then + manageEntry move; + elif [[ ${action} == "3 Delete Password File" ]]; then + manageEntry delete; + elif [[ ${action} == "2 Copy Password File" ]]; then + manageEntry copy; + elif [[ ${action} == "4 Edit Password File" ]]; then + manageEntry edit; + elif [[ ${action} == "5 Generate New Password" ]]; then + generatePass; + elif [[ ${action} == "< Return" ]]; then + mainMenu; + elif [[ ${action} == "" ]]; then + exit + fi +} + +showEntry () { + if [[ -z $pass_content ]]; then + pass_temp=$(PASSWORD_STORE_DIR="${root}" pass show "$selected_password") + password="${pass_temp%%$'\n'*}" + pass_key_value=$(printf '%s\n' "${pass_temp}" | tail -n+2 | grep ': ') + declare -A stuff + + while read -r LINE; do + _id="${LINE%%: *}" + _val="${LINE#* }" + stuff["${_id}"]=${_val} + done < <(printf '%s\n' "${pass_key_value}") + + stuff["pass"]=${password} + + if test "${stuff['autotype']+autotype}"; then + : + else + stuff["autotype"]="${USERNAME_field} :tab pass" + fi + + pass_content="$(for key in "${!stuff[@]}"; do printf '%s\n' "${key}: ${stuff[$key]}"; done)" + fi + + bla_content=("< Return" + "${pass_content}" + ) + + bla=$(printf '%s\n' "${bla_content[@]}" | _rofi -dmenu -mesg "Enter: Copy entry to clipboard" -p "> ") + rofi_exit=$? + + word=$(printf '%s' "$bla" | gawk -F': ' '{print $1}') + + if [[ ${rofi_exit} -eq 1 ]]; then + exit + elif [[ ${rofi_exit} -eq 0 ]]; then + if [[ ${bla} == "< Return" ]]; then + mainMenu + else + if [[ -z $(printf '%s' "${stuff[${word}]}") ]]; then + printf '%s' "$word" | doClip + else + printf '%s' "${stuff[${word}]}" | doClip + fi + if [[ $notify == "true" ]]; then + notify-send "passmenu" "Copied Password\\nClearing in $clip_clear seconds" + fi + if [[ $notify == "true" ]]; then + (sleep $clip_clear; printf '%s' "" | _clip_in_primary; printf '%s' "" | _clip_in_clipboard | notify-send "passmenu" "Clipboard cleared") & + elif [[ $notify == "false" ]]; then + (sleep $clip_clear; printf '%s' "" | _clip_in_primary; printf '%s' "" | _clip_in_clipboard) & + fi + exit + fi + fi + exit + unset stuff + unset password + unset selected_password + unset password_temp + unset stuff + exit +} + +manageEntry () { + if [[ "$1" == "edit" ]]; then + EDITOR=$EDITOR PASSWORD_STORE_DIR="${root}" pass edit "${selected_password}" + mainMenu + elif [[ $1 == "move" ]]; then + cd "${root}" || exit + group_array=(*/) + group=$(printf '%s\n' "${group_array[@]%/}" | _rofi -dmenu -p "Choose Group > ") + if [[ $group == "" ]]; then + exit + fi + PASSWORD_STORE_DIR="${root}" pass mv "$selected_password" "${group}" + mainMenu + elif [[ $1 == "copy" ]]; then + cd "${root}" || exit + group_array=(*/) + group=$(printf '%s\n' "${group_array[@]%/}" | _rofi -dmenu -p "Choose Group > ") + if [[ $group == "" ]]; then + exit + else + new_name="$(listgpg | _rofi -dmenu -format 'f' -mesg "Copying to same Group. Please enter a name for the new entry" -p "> ")" + fi + PASSWORD_STORE_DIR="${root}" pass cp "$selected_password" "${group}/${new_name}" + mainMenu + elif [[ "$1" == "delete" ]]; then + HELP="Selected entry: ${selected_password}" + ask_content=("Yes" + "No" + ) + ask=$(printf '%s\n' "${ask_content[@]}" | _rofi -mesg "${HELP}" -dmenu -p "Are You Sure? > ") + if [[ "$ask" == "Yes" ]]; then + PASSWORD_STORE_DIR="${root}" pass rm --force "${selected_password}" + elif [[ "$ask" == "No" ]]; then + mainMenu + elif [[ -z "$ask" ]]; then + exit + fi + else + mainMenu + fi +} + +edit_pass() { + if [[ $edit_new_pass == "true" ]]; then + PASSWORD_STORE_DIR="${root}" pass edit "${1}" + fi +} + +insertPass () { + url=$(_clip_out_clipboard) + + if [[ "${url:0:4}" == "http" ]]; then + domain_name="$(printf '%s\n' "${url}" | awk -F / '{l=split($3,a,"."); print (a[l-1]=="com"?a[l-2] OFS:X) a[l-1] OFS a[l]}' OFS=".")" + help_content="Domain: ${domain_name} + Type name, make sure it is unique" + else + help_content="Hint: Copy URL to clipboard before calling this menu. + Type name, make sure it is unique" + fi + + cd "${root}" || exit + group_array=(*/) + grouplist=$(printf '%s\n' "${group_array[@]%/}") + name="$(listgpg | _rofi -dmenu -format 'f' -filter "${domain_name}" -mesg "${help_content}" -p "> ")" + val=$? + + if [[ $val -eq 1 ]]; then + exit + fi + + user_content=("${default_user2}" + "${USER}" + "${default_user}" + ) + + user=$(printf '%s\n' "${user_content[@]}" | _rofi -dmenu -mesg "Chose Username or type" -p "> ") + val=$? + + if [[ $val -eq 1 ]]; then + exit + fi + + group_content=("No Group" + "---" + "${grouplist}" + ) + + group=$(printf '%s\n' "${group_content[@]}" | _rofi -dmenu -p "Choose Group > ") + val=$? + + if [[ $val -eq 1 ]]; then + exit + fi + + pw=$(printf '%s' "Generate" | _rofi -dmenu -password -p "Password > " -mesg "Type Password or hit Enter to generate one") + + if [[ $pw == "Generate" ]]; then + pw=$(_pwgen "${password_length}") + fi + + clear + + if [[ "$group" == "No Group" ]]; then + if [[ $url == http* ]]; then + pass_content=("${pw}" + "---" + "${USERNAME_field}: ${user}" + "${URL_field}: ${url}" + ) + printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${name}" > /dev/null && edit_pass "${name}" + else + pass_content=("${pw}" + "---" + "${USERNAME_field}: ${user}" + ) + printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${name}" > /dev/null && edit_pass "${name}" + fi + else + if [[ $url == http* ]]; then + pass_content=("${pw}" + "---" + "${USERNAME_field}: ${user}" + "${URL_field}: ${url}" + ) + printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${group}/${name}" > /dev/null && edit_pass "${group}/${name}" + else + pass_content=("${pw}" + "---" + "${USERNAME_field}: ${user}" + ) + printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${group}/${name}" > /dev/null && edit_pass "${group}/${name}" + fi + fi +} + +help_msg () { + cat <<'EOF' + Usage: + passmenu [command] + + Commands: + --insert insert new entry to password store + --root set custom root directories (colon separated) + --last-used highlight last used item + --show-last show details of last used Entry + --bmarks start in bookmarks mode + + passmenu version 1.0.0 +EOF +} + +get_config_file () { + configs=("$ROFI_PASS_CONFIG" + "$config_dir/rofi/passmenu/config" + "/etc/passmenu.conf") + + # return the first config file with a valid path + for config in "${configs[@]}"; do + # '-n' is needed in case ROFI_PASS_CONFIG is not set + if [[ -n "${config}" && -f "${config}" ]]; then + printf "%s" "$config" + return + fi + done +} + +main () { + # load config file + config_file="$(get_config_file)" + [[ -n "$config_file" ]] && source "$config_file" + + # create tmp dir + if [[ ! -d "$cache_dir/passmenu" ]]; then + mkdir -p "$cache_dir/passmenu" + fi + + # fix keyboard layout if enabled in config + if [[ $fix_layout == "true" ]]; then + layout_cmd + fi + + # set help color + if [[ $help_color == "" ]]; then + help_color=$(rofi -dump-xresources | grep 'rofi.color.normal' | gawk -F ',' '/,/{gsub(/ /, "", $2); print $2}') + fi + + # check for BROWSER variable, use xdg-open as fallback + if [[ -z $BROWSER ]]; then + export BROWSER=xdg-open + fi + + # check if alternative root directory was given on commandline + if [[ -r "$cache_dir/passmenu/last_used" ]] && [[ $1 == "--last-used" || $1 == "--show-last" ]]; then + roots=("$(awk -F ': ' '{ print $1 }' "$cache_dir/passmenu/last_used")") + elif [[ -n "$2" && "$1" == "--root" ]]; then + custom_root=true; IFS=: read -r -a roots <<< "$2" + elif [[ -n $root ]]; then + custom_root=true; IFS=: read -r -a roots <<< "${root}" + elif [[ -n ${PASSWORD_STORE_DIR} ]]; then + roots=("${PASSWORD_STORE_DIR}") + else + roots=("$HOME/.password-store") + fi + roots_index=0 + roots_length=${#roots[@]} + export root=${roots[$roots_index]} + export PASSWORD_STORE_DIR="${root}" + case $1 in + --insert) + insertPass + ;; + --root) + mainMenu + ;; + --help) + help_msg + ;; + --last-used) + if [[ -r "$cache_dir/passmenu/last_used" ]]; then + entry="$(awk -F ': ' '{ print $2 }' "$cache_dir/passmenu/last_used")" + fi + mainMenu + ;; + --show-last) + if [[ -r "$cache_dir/passmenu/last_used" ]]; then + selected_password="$(awk -F ': ' '{ print $2 }' "$cache_dir/passmenu/last_used")" viewEntry + else + mainMenu + fi + ;; + --bmarks) + mainMenu --bmarks; + ;; + *) + mainMenu + ;; + esac +} + +main "$@" diff --git a/.local/bin/rofi/powermenu b/.local/bin/rofi/powermenu new file mode 100755 index 000000000..9f1e5514b --- /dev/null +++ b/.local/bin/rofi/powermenu @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# powermenu +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 07 December 2022, 11:01:52 PM [GMT] +#Modified: +# Sat 02 September 2023, 12:11:51 AM [GMT+1] +# +#Description: +# This script defines just a mode for rofi instead of being a self-contained +# executable that launches rofi by itself. This makes it more flexible than +# running rofi inside this script as now the user can call rofi as one pleases. +# For instance: +# +# rofi -show powermenu -config ~/.config/rofi/powermenu.rasi +# +#Dependencies: +# +# +# shellcheck disable=all + +if [ -z "${ROFI_OUTSIDE}" ]; then + echo "run this script in rofi". + exit +fi + +set -e +set -u + +# All supported choices +all=(shutdown reboot suspend hibernate logout lockscreen) + +# By default, show all (i.e., just copy the array) +show=("${all[@]}") + +declare -A texts +texts[lockscreen]="lock screen" +texts[logout]="log out" +texts[suspend]="suspend" +texts[hibernate]="hibernate" +texts[reboot]="reboot" +texts[shutdown]="shut down" + +declare -A icons +icons[lockscreen]="" +icons[logout]="󰍃" +icons[suspend]="󰒲" +icons[hibernate]="󰋊" +icons[reboot]="󰜉" +icons[shutdown]="󰐥" +icons[cancel]="" + +declare -A actions +actions[lockscreen]="loginctl lock-session" +actions[logout]="pkill -KILL -u ${USER}" +actions[suspend]="systemctl suspend" +actions[hibernate]="systemctl hibernate" +actions[reboot]="systemctl reboot" +actions[shutdown]="systemctl poweroff" + +# By default, ask for confirmation for actions that are irreversible +confirmations=(reboot shutdown logout) + +# By default, no dry run +dryrun=false +showsymbols=true + +getuptime() { + state=$(uptime | sed -E 's/^[^,]*up *//; s/mins/minutes/; s/hrs?/hours/; + s/([[:digit:]]+):0?([[:digit:]]+)/\1 hours, \2 minutes/; + s/^1 hours/1 hour/; s/ 1 hours/ 1 hour/; + s/min,/minutes,/; s/ 0 minutes,/ less than a minute,/; s/ 1 minutes/ 1 minute/; + s/ / /; s/, *[[:digit:]]* users?.*//') +} + +function check_valid { + option="$1" + shift 1 + for entry in "${@}"; do + if [ -z "${actions[$entry]+x}" ]; then + echo "Invalid choice in $option: $entry" >&2 + exit 1 + fi + done +} + +# Parse command-line options +parsed=$(getopt --options=h --longoptions=help,dry-run,confirm:,choices:,choose:,symbols,no-symbols --name "$0" -- "$@") +if [ $? -ne 0 ]; then + echo 'Terminating...' >&2 + exit 1 +fi +eval set -- "$parsed" +unset parsed +while true; do + case "$1" in + "-h" | "--help") + echo "rofi-power-menu - a power menu mode for Rofi" + echo + echo "Usage: rofi-power-menu [--choices CHOICES] [--confirm CHOICES]" + echo " [--choose CHOICE] [--dry-run] [--symbols|--no-symbols]" + echo + echo "Use with Rofi in script mode. For instance, to ask for shutdown or reboot:" + echo + echo " rofi -show menu -modi \"menu:rofi-power-menu --choices=shutdown/reboot\"" + echo + echo "Available options:" + echo " --dry-run Don't perform the selected action but print it to stderr." + echo " --choices CHOICES Show only the selected choices in the given order. Use / " + echo " as the separator. Available choices are lockscreen, logout," + echo " suspend, hibernate, reboot and shutdown. By default, all" + echo " available choices are shown." + echo " --confirm CHOICES Require confirmation for the gives choices only. Use / as" + echo " the separator. Available choices are lockscreen, logout," + echo " suspend, hibernate, reboot and shutdown. By default, only" + echo " irreversible actions logout, reboot and shutdown require" + echo " confirmation." + echo " --choose CHOICE Preselect the given choice and only ask for a confirmation" + echo " (if confirmation is set to be requested). It is strongly" + echo " recommended to combine this option with --confirm=CHOICE" + echo " if the choice wouldn't require confirmation by default." + echo " Available choices are lockscreen, logout, suspend," + echo " hibernate, reboot and shutdown." + echo " --[no-]symbols Show Unicode symbols or not. Requires a font with support" + echo " for the symbols. Use, for instance, fonts from the" + echo " Nerdfonts collection. By default, they are shown" + echo " -h,--help Show this help text." + exit 0 + ;; + "--dry-run") + dryrun=true + shift 1 + ;; + "--confirm") + IFS='/' read -ra confirmations <<<"$2" + check_valid "$1" "${confirmations[@]}" + shift 2 + ;; + "--choices") + IFS='/' read -ra show <<<"$2" + check_valid "$1" "${show[@]}" + shift 2 + ;; + "--choose") + # Check that the choice is valid + check_valid "$1" "$2" + selectionID="$2" + shift 2 + ;; + "--symbols") + showsymbols=true + shift 1 + ;; + "--no-symbols") + showsymbols=false + shift 1 + ;; + "--") + shift + break + ;; + *) + echo "Internal error" >&2 + exit 1 + ;; + esac +done + +# Define the messages after parsing the CLI options so that it is possible to +# configure them in the future. + +function write_message { + icon="$1" + text="$2" + if [ "$showsymbols" = "true" ]; then + echo -n "\u200e$icon \u2068$text\u2069" + else + echo -n "$text" + fi +} + +function print_selection { + echo -e "$1" | $( + read -r -d '' entry + echo "echo $entry" + ) +} + +declare -A messages +declare -A confirmationMessages +for entry in "${all[@]}"; do + messages[$entry]=$(write_message "${icons[$entry]}" "${texts[$entry]^}") +done +for entry in "${all[@]}"; do + confirmationMessages[$entry]=$(write_message "${icons[$entry]}" "Yes, ${texts[$entry]}") +done +confirmationMessages[cancel]=$(write_message "${icons[cancel]}" "No, cancel") + +if [ $# -gt 0 ]; then + # If arguments given, use those as the selection + selection="${*}" +else + # Otherwise, use the CLI passed choice if given + if [ -n "${selectionID+x}" ]; then + selection="${messages[$selectionID]}" + fi +fi + +# Don't allow custom entries +echo -e "\0no-custom\x1ftrue" + +echo -e "\0markup-rows\x1ftrue" + +if [ -z "${selection+x}" ]; then + getuptime + echo -e "\0prompt\x1fPower Menu" + echo -en "\0message\x1fUptime :: ${state^}\n" + for entry in "${show[@]}"; do + echo -e "${messages[$entry]}\0icon\x1f${icons[$entry]}" + done +else + for entry in "${show[@]}"; do + if [ "$selection" = "$(print_selection "${messages[$entry]}")" ]; then + # Check if the selected entry is listed in confirmation requirements + for confirmation in "${confirmations[@]}"; do + if [ "$entry" = "$confirmation" ]; then + # Ask for confirmation + echo -e "\0prompt\x1fAre you sure" + echo -e "${confirmationMessages[$entry]}\0icon\x1f${icons[$entry]}" + echo -e "${confirmationMessages[cancel]}\0icon\x1f${icons[cancel]}" + exit 0 + fi + done + # If not, then no confirmation is required, so mark confirmed + selection=$(print_selection "${confirmationMessages[$entry]}") + fi + if [ "$selection" = "$(print_selection "${confirmationMessages[$entry]}")" ]; then + if [ $dryrun = true ]; then + # Tell what would have been done + echo "Selected: $entry" >&2 + else + # Perform the action + ${actions[$entry]} + fi + exit 0 + fi + if [ "$selection" = "$(print_selection "${confirmationMessages[cancel]}")" ]; then + # Do nothing + exit 0 + fi + done + # The selection didn't match anything, so raise an error + echo "Invalid selection: $selection" >&2 + exit 1 +fi diff --git a/.local/bin/rofi/powermenu_run b/.local/bin/rofi/powermenu_run new file mode 100755 index 000000000..4dc246c41 --- /dev/null +++ b/.local/bin/rofi/powermenu_run @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# powermenu_run +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 07 December 2022, 11:01:52 PM [GMT] +#Modified: +# Sun 06 August 2023, 11:40:51 AM [GMT+1] +# +#Description: +# +# +#Dependencies: +# +# +# shellcheck disable=all + +rofi -show powermenu -config ~/.config/rofi/powermenu.rasi \ No newline at end of file diff --git a/.local/bin/rofi/wifimanager b/.local/bin/rofi/wifimanager new file mode 100755 index 000000000..bf80ce445 --- /dev/null +++ b/.local/bin/rofi/wifimanager @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# wifimanager +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 07 December 2022, 11:01:26 PM [GMT] +#Modified: +# Mon 16 January 2023, 05:33:28 PM [GMT] +# +#Description: +# +# +#Dependencies: +# rofi nmcli(Network Manager) dunst +# +# shellcheck disable=all + +if [ -z "${ROFI_OUTSIDE}" ] +then + echo "run this script in rofi". + exit +fi + +CACHE_DIR="$HOME/.cache/rofi/network" +[ ! -d $CACHE_DIR ] && mkdir -p "$CACHE_DIR" + +station=$(nmcli device | grep "wifi " | awk '{ printf $1 }') # Device Station name + +function network_status () { + state=$(nmcli -fields WIFI g | sed -n 2p | xargs) # Wifi enbaled or disabled + connection=$(nmcli connection show --active | grep "wifi" | awk -F "wifi" '{ print $1 }' | sed -e '1{s/[^ ]\+\s*$//}') +} + +# Don't allow custom entries +echo -e "\0no-custom\x1ftrue" + +function network_options() { + if [[ "$state" == "enabled" ]]; then + if [ -z "$connection" ]; then + echo "-=|> Connect to a Network" + echo "-=|> Connect to a Hidden Network" + connection="N/A" + elif [ ! -z "$connection" ]; then + echo "-=|> Connect to a Network" + echo "-=|> Connect to a Hidden Network" + echo "-=|> Disconnect Current Network" + fi + echo "-=|> Disable Wifi" + elif [[ "$state" =~ "disabled" ]]; then + echo "-=|> Enable Wifi" + fi +} + +function main_scrn() { # Main window to be displayed on start + rm $CACHE_DIR/* >/dev/null 2>&1 + network_status + network_options + echo -en "\x00prompt\x1fWifi\n" + echo -en "\0message\x1fWIFI Status :: ${state^}\t\tConnected to :: ${connection}\n" + echo -en "-=|> Delete existing connections\n" + echo -en "-=|> Refresh\n" +} + +function show_available_networks(){ # Output the list of available Wifi networks + nmcli -f BSSID,SSID device wifi list > "$CACHE_DIR/available_networks" + echo -en "\0message\x1f $(cat "$CACHE_DIR/available_networks" | sed '1!d')\n" + echo "$(cat "$CACHE_DIR/available_networks" | sed '1d')" + echo "-=|> Rescan available networks" + echo "<|=- To Previous Menu" +} + +function saved_connections() { + echo "delete_network" > "$CACHE_DIR/delete_connection" + echo -en "\0message\x1f\t\t\t\t\t\t[Enter] - Delete selected connection\n" + echo -en "UUID\t\t\t\t TYPE\tNAME\0nonselectable\x1ftrue\n" + echo -en "\0active\x1f0\n" + nmcli --fields UUID,TYPE,NAME con show | grep wifi + echo "<|=- To Previous Menu" +} + +function error_status() { + if [[ "$status" =~ "Connection successfully activated " ]]; then # If connected successfully, then notify and exit + notify-send "Connected to " "$SSID" + exit 0 + elif [[ "$status" =~ "Device '$station' successfully activated with " ]]; then # If connected successfully, then notify and exit + notify-send "Network " "$(echo $status | cut -c 6-)" + rm $CACHE_DIR/* + exit 0 + else + if [ -f $CACHE_DIR/network_ssid ]; then + echo -en "\0message\x1f! Connection Failed. This is normal if connecting to a hidden network, Try again.\n" + elif [ -f $CACHE_DIR/network_bssid ]; then + echo -en "\0message\x1f! Connection Failed! Check your password.\n" + fi + echo "$status" # If failed to connect, shows the error code and asks for password + echo "<|=- To Previous Menu" + fi +} + +function get_ssid() { + echo "hidden" > "$CACHE_DIR/get_ssid" + echo -en "\00prompt\x1fEnter SSID\n" + echo -en "\0message\x1f# Type your network SSID (Network Name).\n" + echo "<|=- To Previous Menu" +} + +function get_password() { + echo -en "\00prompt\x1fPassword\n" + echo -en "\0message\x1f# Type your network security passphrase here.\n" + echo "<|=- To Previous Menu" +} + +function restore_view() { + if [ -f $CACHE_DIR/delete_connection ]; then + saved_connections + elif [ -f $CACHE_DIR/available_networks ]; then + show_available_networks + elif [ -f $CACHE_DIR/network_selected ]; then + show_available_networks + elif [ -f $CACHE_DIR/get_ssid ]; then + get_ssid + elif [ -f $CACHE_DIR/network_bssid ] || [ -f $CACHE_DIR/network_ssid ]; then + get_password + else + main_scrn + fi +} + +function show_previous_menu() { + if [ -f $CACHE_DIR/network_bssid ]; then + rm $CACHE_DIR/network_bssid + show_available_networks + elif [ -f $CACHE_DIR/network_ssid ]; then + rm $CACHE_DIR/network_ssid + get_ssid + else + main_scrn + fi +} + +if [[ "${ROFI_RETV}" == "0" ]]; then + main_scrn + +elif [[ "${ROFI_RETV}" == "1" ]]; then + if [[ "$@" == "<|=- To Previous Menu" ]]; then + show_previous_menu + elif [[ "$@" == "-=|> Refresh" ]]; then + main_scrn + elif [[ "$@" == "-=|> Disable Wifi" ]]; then + nmcli radio wifi off + restore_view + elif [[ "$@" == "-=|> Enable Wifi" ]]; then + nmcli radio wifi on + restore_view + elif [[ "$@" == "-=|> Connect to a Network" ]]; then + show_available_networks + elif [[ "$@" == "-=|> Connect to a Hidden Network" ]]; then + get_ssid + elif [[ "$@" == "-=|> Disconnect Current Network" ]]; then # Disconnect any connected network + con_uuid=$(nmcli connection show --active | grep "wifi" | awk -F "wifi" '{print $1}' | awk '{print $NF}' | xargs) + nmcli connection down "$con_uuid" > /dev/null 2>&1 + restore_view + elif [[ "$@" == "-=|> Delete existing connections" ]]; then # Opens the saved networks list window + saved_connections + elif [ -f $CACHE_DIR/delete_connection ]; then + connection_uuid=$(echo "$@" | awk '{print $1}') + nmcli con delete uuid "$connection_uuid" >/dev/null 2>&1 + restore_view + elif [ -f $CACHE_DIR/available_networks ]; then + if [[ "$@" == "-=|> Rescan available networks" ]]; then + nmcli device wifi rescan >/dev/null 2>&1 + restore_view + else + rm $CACHE_DIR/available_networks + echo "$@" > "$CACHE_DIR/network_selected" + BSSID=$(echo "$@" | awk '{print $1}') + SSID=$(echo "$@" | cut -c 20- | xargs) + SECURITY=$(nmcli -f BSSID,SECURITY device wifi | grep "$BSSID" | awk -F " " '{print $2}' | xargs) + if [[ $(nmcli -f NAME con show | grep -o -m 1 "$SSID") == "$SSID" ]]; then + status=$(nmcli connection up "$SSID") + error_status + exit 0 + elif [[ "$SECURITY" =~ "--" ]] || [ -z "$SECURITY" ]; then + nmcli device wifi connect "$BSSID" >/dev/null 2>&1 + notify-send "Successfully connected to " "$SSID" + elif [[ "$SECURITY" =~ "WPA" ]] || [ "$SECURITY" =~ "WEP" ]; then + rm $CACHE_DIR/network_selected + echo "$BSSID" > "$CACHE_DIR/network_bssid" + get_password + fi + fi + fi + +elif [[ "${ROFI_RETV}" == "2" ]]; then + if [ -f $CACHE_DIR/get_ssid ]; then + rm $CACHE_DIR/get_ssid + echo "$@" > "$CACHE_DIR/network_ssid" + get_password + elif [ -f $CACHE_DIR/network_bssid ]; then + PASSWORD="$@" + BSSID=$(cat "$CACHE_DIR/network_bssid") + status=$(nmcli device wifi connect "$BSSID" password "$PASSWORD") + error_status + elif [ -f $CACHE_DIR/network_ssid ]; then + PASSWORD="$@" + SSID=$(cat "$CACHE_DIR/network_ssid") + status=$(nmcli device wifi connect "$SSID" password "$PASSWORD" hidden true) + error_status + else + restore_view + fi + +else + restore_view + echo -en "\0message\x1f# Custom key events are not valid in this mode.\n" + +fi diff --git a/.local/bin/rofi/wifimanager_run b/.local/bin/rofi/wifimanager_run new file mode 100755 index 000000000..9cac356cc --- /dev/null +++ b/.local/bin/rofi/wifimanager_run @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# wifimanager_run +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Wed 07 December 2022, 11:01:52 PM [GMT] +#Modified: +# Sat 16 September 2023, 07:42:10 PM [UTC] +# +#Description: +# +# +#Dependencies: +# +# +# shellcheck disable=all + +rofi -show wifimanager -config ~/.config/rofi/wifimanager.rasi diff --git a/.local/bin/rofi/youtube_subs b/.local/bin/rofi/youtube_subs new file mode 100755 index 000000000..958821811 --- /dev/null +++ b/.local/bin/rofi/youtube_subs @@ -0,0 +1,269 @@ +#!/usr/bin/env bash +#-*-coding:utf-8 -*- +#Auto updated? +# Yes +#File: +# youtube_subs +#Author: +# The-Repo-Club [wayne6324@gmail.com] +#Github: +# https://github.com/The-Repo-Club/ +# +#Created: +# Sun 03 January 2021, 05:09:33 PM [GMT] +#Modified: +# Fri 25 August 2023, 05:45:05 PM [GMT+1] +# +#Description: +# Watch your youtube subscriptions without a youtube account +# via curl, repomenu, browser and basic unix commands. +# +# The $SUBS_FILE is a text file containing usernames or channel IDs +# comments and blank lines are ignored. +# +# +#Dependencies: +# rofi +# + +rofis() { + rofi -dmenu -p "Youtube Subs:" -mesg "Select a video." +} + +# -/-/-/-/- Settings -/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/ +: "${SUBS_FILE:=${HOME}/.config/rofi/configs/subs.ini}" +: "${SUBS_MENU_PROG:=rofis}" +: "${SUBS:=${HOME}/.cache/subs}" +: "${SUBS_LINKS:=$SUBS/links}" +: "${SUBS_CACHE:=$SUBS/cache}" +: "${SUBS_SLEEP_VALUE:=0.05}" # raise this if you experience problems +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SEP=^^^^^ # shouldn't need to change this +SUBS_OPEN="mpv --volume=50" +# -/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/-/ + +die() { + printf >&2 '%s\n' "$*" + exit 1 +} + +usage() { + die 'Usage: youtube_subs [-c cat_subs] [-g gen_links] [-u update_subs] [-d daemonize]' +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Synopsis: $SUBS_FILE [txt] -> $SUBS_LINKS [xml links] +# +# Updates local cache of xml subscription links from the +# subscription file containing either usernames or channel ids. +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +gen_links() { + : >"$SUBS_LINKS" + + count=0 + total=$(sed -e '/^$/d' -e '/^#/d' <"$SUBS_FILE" | wc -l) + + while read -r line; do + + # ignore comments and blank lines + case $line in '' | ' ' | '#'*) continue ;; esac + + # strip off in-line comments and any trailing whitespace + line=${line%%#*} + line=${line%% *} + + count=$((count + 1)) + + case $line in + UC*) + # YT channel IDs always begin with 'UC' and are 24 chars long + printf "[%s/%s] using channel ID '%s' for xml link\n" "$count" "$total" "$line" + + [ ${#line} -eq 24 ] && + printf 'https://youtube.com/feeds/videos.xml?%s\n' \ + "channel_id=$line" >>"$SUBS_LINKS" + ;; + *) + # otherwise we are given a username, we must find out its channel ID + printf "fetching channel ID for %s...\n" "$line" + + curl -sfL --retry 10 "https://youtube.com/user/$line/about" | + while read -r line; do + case $line in + *channel/UC??????????????????????*) + line=${line##*channel/} + line=${line%%\"*} + printf "[%s/%s] using channel ID '%s' for xml link\n" "$count" "$total" "$line" + printf 'https://youtube.com/feeds/videos.xml?channel_id=%s\n' \ + "$line" >>"$SUBS_LINKS" + break + ;; + esac + done & + sleep "${SUBS_SLEEP_VALUE:-0}" + ;; + esac + + done <"$SUBS_FILE" + + count=0 + while [ "$count" -ne "$total" ]; do + count=$(wc -l <"$SUBS_LINKS") + printf "[%s/%s] waiting for jobs to complete...\n" "$count" "$total" + sleep 0.5 + done +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Synopsis: $1 [LINK] -> $SUBS_CACHE/$chan_name/concat [CHANNEL INFO] +# +# Takes a channel rss feed link and creates a file +# with a line of its videos dates, titles, and urls. +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +get_vids() { + data=$(curl -sfL --retry 15 "$1") + + # hide the first tag which is the channel + # creation date + data=${data#*\<\/published\>} + + # trim off outer tags + chan=${data%%} + + printf "%s\n" "$data" | + while read -r line; do + case $line in + *'link rel='*) + line=${line#*href=\"} + line=${line%\"/\>} + line=https://${line#*www.} + url=$line + ;; + *''*) + line=${line%+00:*} + line=${line#*} + date=$line + ;; + *''*) + line=${line%} + title=$line + printf '%s\n' \ + "${date}${SEP}${chan}${SEP}${title}${SEP}${url}" \ + >>"$SUBS_CACHE/$chan" + ;; + esac + done +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Updates the local cache of subscriptions. ([-u] flag) +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +update_subs() { + [ -f "$SUBS_LINKS" ] || die 'Subs links have not been generated.' + + rm -r "${SUBS_CACHE:-?}" 2>/dev/null || : + mkdir -p "$SUBS_CACHE" + + total=$(wc -l <"$SUBS_LINKS") + + count=0 + while read -r link; do + count=$((count + 1)) + printf 'starting job [%s/%s] for %s\n' "$count" "$total" "$link" + get_vids "$link" & + sleep "${SUBS_SLEEP_VALUE:-0}" + done <"$SUBS_LINKS" + + count=0 + while [ "$count" -ne "$total" ]; do + count=$(printf '%s\n' "$SUBS_CACHE"/* | wc -l) + printf "[%s/%s] waiting for fetch jobs to complete...\n" "$count" "$total" + sleep 0.5 + done + + printf '%s\n\n' 'done!' +} + +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +# Grab current cache of subscriptions, sort by date uploaded +# -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +cat_subs() { + sort -r "$SUBS_CACHE"/* | + while read -r line; do + chan=${line#*$SEP} + chan=${chan%%$SEP*} + title=${line#*$chan$SEP} + title=${title%%$SEP*} + date=${line%%$SEP*} + date=${date#*-} + date=${date%T*} + printf '[%s %s] %s\n' "$date" "$chan" "$title" + done +} + +# Split the concatenated lines into entities, send to menu program. +# Finally, play the result with mpv. +get_sel() { + if [ -d "$SUBS_CACHE" ]; then + sel=$(cat_subs | $SUBS_MENU_PROG) + else + die 'Subs cache has not been retrieved.' + fi + + [ "$sel" ] || die Interrupted + + chan="${sel#* }" + chan="${chan%%] *}" + title=${sel#*"$chan"\] } + while read -r line; do + case $line in + *"$SEP$title$SEP"*) + url=${line##*$SEP} + if [ "$url" ]; then + printf 'playing: %s\n' "$url" + # Play the selection. + # shellcheck disable=2086 + exec $SUBS_OPEN "$url" + fi + break + ;; + esac + done <"$SUBS_CACHE/$chan" +} + +daemonize() { + # create a cached copy of the subs file to check for changes + # if changes occur, re-generate links automatically + daemon_file=${HOME}/.cache/subs_daemon.cache + if [ ! -f "$daemon_file" ]; then + cp -f "${SUBS_FILE:=${HOME}/.config/repomenu/subs.ini}" "$daemon_file" + fi + + while true; do + if ! cmp "${SUBS_FILE:=${HOME}/.config/repomenu/subs.ini}" "$daemon_file"; then + cp -f "${SUBS_FILE:=${HOME}/.config/repomenu/subs.ini}" "$daemon_file" + fi + gen_links + update_subs + interval=${SUBS_DAEMON_INTERVAL:-$((10 * 60))} + printf 'Sleeping for %s seconds...\n' "$interval" + sleep "$interval" + done +} + +main() { + mkdir -p "$SUBS" + + case ${1#-} in + h) usage ;; + g) gen_links ;; + u) update_subs ;; + c) cat_subs ;; + d) daemonize ;; + *) get_sel ;; + esac +} + +main "$@"