summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbenji_cleaner.sh37
-rwxr-xr-xcliparmyknife.sh299
-rwxr-xr-xclipboard_treater.sh36
-rw-r--r--dependencies/GIMPsRGB.iccbin0 -> 672 bytes
-rw-r--r--dependencies/JapanColor2011Coated.iccbin0 -> 1979304 bytes
-rwxr-xr-xdl_helper.sh103
-rwxr-xr-xdlanor_critic.sh11
-rwxr-xr-xffmgrep.sh59
-rw-r--r--firewallsetup/README.md28
-rw-r--r--firewallsetup/firewall67
-rw-r--r--firewallsetup/firewall-down15
-rw-r--r--firewallsetup/firewall-reload3
-rwxr-xr-xhackshot.sh90
-rwxr-xr-xhamachi_direct_fisher.sh21
-rwxr-xr-xiedupes.sh135
-rwxr-xr-xkeymap_toggler.sh32
-rw-r--r--learning_alarm.sh16
-rwxr-xr-xminivac.sh125
-rw-r--r--miscripts/DICTIONARY9
-rw-r--r--miscripts/Export Chrome Bookmarks as HTML 54
-rw-r--r--miscripts/check_ssh.sh10
-rw-r--r--miscripts/choice.sh23
-rw-r--r--miscripts/delfi_rss.py3
-rw-r--r--miscripts/hypothetical_cliparmyknife_mod.txt9
-rw-r--r--miscripts/hypothetical_picarmyknife.txt11
-rw-r--r--miscripts/imnotgivingup.sh15
-rw-r--r--miscripts/inotifytest.sh9
-rw-r--r--miscripts/minivac_pulse.sh134
-rw-r--r--miscripts/postman.sh117
-rw-r--r--miscripts/qemu_helper.sh0
-rw-r--r--miscripts/rshred.txt65
-rw-r--r--miscripts/shigoto_kfxx.sh114
-rw-r--r--miscripts/shigoto_trivy_sorting_hat.sh66
-rw-r--r--miscripts/snap.sh20
-rw-r--r--miscripts/wide_play_cmus.sh101
-rw-r--r--miscripts/wide_play_dmenu.sh106
-rw-r--r--miscripts/wireguard-install.sh474
-rwxr-xr-xmusicbot_wrapper.sh33
-rwxr-xr-xnotification_wrapper.sh141
-rwxr-xr-xnsxiv_utils/nsxiv-env6
-rwxr-xr-xnsxiv_utils/nsxiv-pipe24
-rwxr-xr-xnsxiv_utils/nsxiv-rifle51
-rwxr-xr-xnsxiv_utils/nsxiv-url32
-rwxr-xr-xpsd_converter.sh127
-rwxr-xr-xpyscripts/anilist_crawl.py45
-rwxr-xr-xpyscripts/crx_getter.py141
-rwxr-xr-xpyscripts/nyu_wide_rsync.py596
-rwxr-xr-xpyscripts/pixiv_auth.py115
-rwxr-xr-xpyscripts/wide_rsync.py563
-rwxr-xr-xscriptlets/adbor.sh49
-rwxr-xr-xscriptlets/androidmount.sh8
-rwxr-xr-xscriptlets/backup_rotation.sh31
-rwxr-xr-xscriptlets/ct_file_handler.sh12
-rwxr-xr-xscriptlets/ct_self_linker.sh9
-rwxr-xr-xscriptlets/die-panel.sh36
-rwxr-xr-xscriptlets/dl_helper_helper.sh17
-rwxr-xr-xscriptlets/drun.sh5
-rwxr-xr-xscriptlets/extractfiles.sh3
-rwxr-xr-xscriptlets/feh_action3.sh20
-rwxr-xr-xscriptlets/find_papes.sh38
-rwxr-xr-xscriptlets/findlast.sh23
-rwxr-xr-xscriptlets/glava_toggler.sh6
-rwxr-xr-xscriptlets/ieset.sh8
-rwxr-xr-xscriptlets/ikaloop.sh5
-rw-r--r--scriptlets/make_exceptions.txt1
-rw-r--r--scriptlets/mute_button.sh3
-rwxr-xr-xscriptlets/old_cliparmyknife.sh295
-rwxr-xr-xscriptlets/old_veepeen_toggler.sh25
-rwxr-xr-xscriptlets/passmenu38
-rw-r--r--scriptlets/passshowcopy.sh3
-rwxr-xr-xscriptlets/peak_or_mid.sh7
-rwxr-xr-xscriptlets/qemu-android.sh27
-rwxr-xr-xscriptlets/qemu-run.sh24
-rwxr-xr-xscriptlets/rss_torrent_helper.sh12
-rwxr-xr-xscriptlets/rxvt_nvim_launcher.sh6
-rwxr-xr-xscriptlets/rxvt_yazi_launcher.sh10
-rwxr-xr-xscriptlets/sidebar_toggler.sh4
-rwxr-xr-xscriptlets/soundpost_dl.sh38
-rwxr-xr-xscriptlets/spacebarspam.sh8
-rwxr-xr-xscriptlets/springcleaning.sh34
-rwxr-xr-xscriptlets/thunar_batch_opener.sh27
-rwxr-xr-xscriptlets/transmission_bell.sh7
-rwxr-xr-xscriptlets/transmission_dirchooser.sh20
-rwxr-xr-xscriptlets/treediff.sh3
-rwxr-xr-xscriptlets/truth.sh20
-rwxr-xr-xscriptlets/unwebp.sh8
-rwxr-xr-xscriptlets/url_processor.sh8
-rwxr-xr-xscriptlets/veepeen_toggler.sh43
-rw-r--r--scriptlets/wine_jp_config.sh13
-rwxr-xr-xscriptlets/wine_jp_run.sh5
-rwxr-xr-xshell-rss-torrent374
-rwxr-xr-xsioyek_db_gen.sh47
-rw-r--r--tgscripts/foundrymacros-thirteenth.txt387
-rw-r--r--tgscripts/fvtt-Macro-infodump.json24
-rw-r--r--tgscripts/fvtt-Macro-stance-ac-change.json24
-rw-r--r--tgscripts/fvtt-Macro-weapon-select.json24
-rw-r--r--tgscripts/roll20macros-dnd.txt48
-rw-r--r--tgscripts/roll20macros-l5r.txt3
-rw-r--r--tgscripts/roll20macros-thirteenth.txt427
-rwxr-xr-xvoice_changer.sh119
-rwxr-xr-xwide_compiler.sh25
-rwxr-xr-xwide_jump.sh144
-rwxr-xr-xwide_play.sh166
-rwxr-xr-xwide_play_helper.sh30
-rwxr-xr-xwide_rotation.sh88
-rw-r--r--winscripts/IISshit1.ps121
-rw-r--r--winscripts/IISshit2.ps135
-rwxr-xr-xwinscripts/OfficeSetup.exebin0 -> 7651536 bytes
-rw-r--r--winscripts/bulk_rename_by_date_created.ps14
-rw-r--r--winscripts/treat_clips.bat3
110 files changed, 7243 insertions, 0 deletions
diff --git a/benji_cleaner.sh b/benji_cleaner.sh
new file mode 100755
index 0000000..cdaf697
--- /dev/null
+++ b/benji_cleaner.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+OLDIFS=$IFS
+IFS=$'\n' # make newlines the only separator
+
+declare -a exprs=("\\\"([^\\\"]+)\\\"|“\1”" "\?|?" "\:|:" "\*|**" ">|>" "<|<" "\|||")
+sedexpr=""
+grepexpr="("
+
+for i in "${exprs[@]}"; do
+ sedexpr+="s|"$i"|g; "
+ grepexpr+="$( cut -d '|' -f 1 <<< "$i" )|"
+done
+
+sedexpr="${sedexpr::-1}"
+grepexpr="${grepexpr::-1})"
+
+for path in $(find "$(pwd)" -type f); do
+ file=$( basename "$path" )
+ [[ ! $( grep -E "$grepexpr" 2>/dev/null <<< "$file" ) ]] && continue
+
+ while true; do
+ question="The following command is about to run: $ mv \"$file\" \"$( sed -r "$sedexpr" <<< "$file" )\""$'\n'"Is this okay [y/n]? "
+ read -p "$question" yn
+ case $yn in
+ [Yy]* ) mv "$file" "$( sed -r "$sedexpr" <<< "$file" )"; break;;
+ [Nn]* ) break;;
+ * ) echo "Please answer yes or no.";;
+ esac
+ done
+
+done
+
+IFS=$OLDIFS
+
+# YOU CAN MAKE THE FILES GLOBAL THOUGH BY JUST REMOVING THE basename CLAUSE, SO DO THAT IF YOU GET ANNOYED AGAIN
+# MAKE IT WORK WITH DIRECTORIES, AS WELL AS ABSOLUTE FILEPATHS (currently you have to run it from the folder of the offending song)
diff --git a/cliparmyknife.sh b/cliparmyknife.sh
new file mode 100755
index 0000000..ca19a44
--- /dev/null
+++ b/cliparmyknife.sh
@@ -0,0 +1,299 @@
+#!/bin/bash
+
+#set -e
+
+get_clip_params () {
+
+ CLIP_PATH=$( dirname "$1" )
+ CLIP_NAME=$( basename "$1" | sed -r "s|\.[^. ]*$||g" )
+ CLIP_EXTENSION=$( sed -r "s|^.*\.||g" <<< "$1" )
+ CLIP_WIDTH=$( ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "$1" )
+ CLIP_HEIGHT=$( ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$1" )
+ CLIP_SAR=$( ffprobe -v error -select_streams v:0 -show_entries stream=sample_aspect_ratio -of default=noprint_wrappers=1:nokey=1 "$1" )
+ CLIP_FRAMERATE=$( ffprobe -i "$1" 2>&1 | grep -oP "\d{1,5}\.?\d{1,5} fps" | tail -1 | sed -r "s|\ fps||g" )
+
+ MOD=1
+
+ [[ "$CLIP_SAR" != "1:1" ]] && MOD=$( echo "$CLIP_SAR" | sed -r "s|\:|\ \/\ |g; s|^|scale\=5\;\ |g" | bc ) && CLIP_HEIGHT=$( printf "%.0f" $( bc <<< "$CLIP_HEIGHT / $MOD" ) )
+
+}
+
+dump_clip_params () {
+
+ echo -e "\nCLIP NAME: $CLIP_NAME.$CLIP_EXTENSION\nCLIP LOCATION: $CLIP_PATH/\nCLIP DIMENSIONS: ${CLIP_WIDTH}x${CLIP_HEIGHT}\nCLIP FRAMERATE: $CLIP_FRAMERATE\nCLIP SAR: $CLIP_SAR"
+
+}
+
+crop () {
+
+ echo -e "-----out_w is the width of the output rectangle-----\n-----out_h is the height of the output rectangle-----\n-----x and y specify the top left corner of the output rectangle-----"
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: ${CLIP_WIDTH}x${CLIP_HEIGHT}"
+
+
+
+ re='^[0-9]+(\ )+[0-9]+(\ )+[0-9]+(\ )+[0-9]+$'
+
+ declare OUT_W OUT_H X Y CROP_PARAMS
+
+ while read -p "Enter new clip dimensions [FORMAT: out_w out_h x y]: " CROP_PARAMS; do
+
+ CROP_PARAMS=${CROP_PARAMS:-"$CLIP_WIDTH $CLIP_HEIGHT 0 0"}
+
+ if ! [[ $CROP_PARAMS =~ $re ]]; then
+ echo "error: Invalid crop parameters" >&2; continue
+ fi
+
+
+
+ OUT_W=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 1 )
+ OUT_H=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 2 )
+ X=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 3 )
+ Y=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 4 )
+
+ if [[ $( bc <<< "$OUT_W + $X" ) -gt $CLIP_WIDTH || $( bc <<< "$OUT_H + $Y" ) -gt $CLIP_HEIGHT ]]; then
+ echo "error: Crop parameters go outside clip bounds" >&2; continue
+ fi
+
+ break
+ done
+
+ OUT_H_MOD=$( bc <<< "$OUT_H * $MOD" )
+ Y_MOD=$( bc <<< "$Y * $MOD" )
+
+ ffmpeg -i "$1" -filter:v "crop=$OUT_W:$OUT_H_MOD:$X:$Y_MOD" "$CLIP_NAME [cropped $OUT_W:$OUT_H:$X:$Y].$CLIP_EXTENSION"
+
+}
+
+join_no_reencode () {
+ [[ ! -f "join.$CLIP_EXTENSION" ]] && cp "$1" "join.$CLIP_EXTENSION" && return
+ echo "file 'join.${CLIP_EXTENSION}'" > cliparmyknife_concat.txt
+ echo "file '$1'" >> cliparmyknife_concat.txt
+ ffmpeg -y -f concat -safe 0 -i cliparmyknife_concat.txt -c copy join.$CLIP_EXTENSION
+ rm cliparmyknife_concat.txt
+}
+
+join_yes_reencode () {
+
+
+ [[ ! -f "join.$CLIP_EXTENSION" ]] && cp "$1" "join.$CLIP_EXTENSION" && return
+ JOIN_WIDTH=$( ffprobe -i "join.$CLIP_EXTENSION" 2>&1 | grep -oP "[1-9]\d{1,10}x[1-9]\d{1,10}" | sed -r "s|\ ||g; s|x[0-9]*$||g" )
+ JOIN_HEIGHT=$( ffprobe -i "join.$CLIP_EXTENSION" 2>&1 | grep -oP "[1-9]\d{1,10}x[1-9]\d{1,10}" | sed -r "s|\ ||g; s|^[0-9]*x||g" )
+
+ #ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i boximouto.webm -c:v copy -c:a libopus -shortest boximouto2.webm
+ #ffprobe -i ikadiabetes2.gif -show_streams -select_streams a -loglevel error
+
+ [[ -z $(ffprobe -i "$1" -show_streams -select_streams a -loglevel error) ]] && ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "$1" -c:v copy -c:a libopus -shortest "/tmp/clip_treated.$CLIP_EXTENSION" || cp "$1" "/tmp/clip_treated.$CLIP_EXTENSION"
+ [[ -z $(ffprobe -i "join.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) ]] && ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "join.$CLIP_EXTENSION" -c:v copy -c:a libopus -shortest "/tmp/join_treated.$CLIP_EXTENSION" || cp "join.$CLIP_EXTENSION" "/tmp/join_treated.$CLIP_EXTENSION"
+
+
+ echo "JOIN DIMENSIONS: $JOIN_WIDTH x $JOIN_HEIGHT"
+
+ [[ $JOIN_WIDTH -gt $CLIP_WIDTH ]] && PAD_WIDTH=$JOIN_WIDTH || PAD_WIDTH=$CLIP_WIDTH
+ [[ $JOIN_HEIGHT -gt $CLIP_HEIGHT ]] && PAD_HEIGHT=$JOIN_HEIGHT || PAD_HEIGHT=$CLIP_HEIGHT
+ PAD_X=$( bc <<< "($JOIN_WIDTH - $CLIP_WIDTH) / 2" )
+ PAD_Y=$( bc <<< "($JOIN_HEIGHT - $CLIP_HEIGHT) / 2" )
+
+ echo "PAD DIMENSIONS: $PAD_X, $PAD_Y"
+
+ if [[ $PAD_X -ge 0 && $PAD_Y -ge 0 ]]; then
+ cp "/tmp/join_treated.$CLIP_EXTENSION" "/tmp/join_temp.$CLIP_EXTENSION"
+ ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=$PAD_X:y=$PAD_Y:color=black" "/tmp/clip_temp.$CLIP_EXTENSION"
+ elif [[ $PAD_X -lt 0 && $PAD_Y -ge 0 ]]; then
+ ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=${PAD_X#-}:y=0:color=black" "/tmp/join_temp.$CLIP_EXTENSION"
+ ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=0:y=$PAD_Y:color=black" "/tmp/clip_temp.$CLIP_EXTENSION"
+ elif [[ $PAD_X -ge 0 && $PAD_Y -lt 0 ]]; then
+ ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=0:y=${PAD_Y#-}:color=black" "/tmp/join_temp.$CLIP_EXTENSION"
+ ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=$PAD_X:y=0:color=black" "/tmp/clip_temp.$CLIP_EXTENSION"
+ elif [[ $PAD_X -lt 0 && $PAD_Y -lt 0 ]]; then
+ ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=${PAD_X#-}:y=${PAD_Y#-}:color=black" "/tmp/join_temp.$CLIP_EXTENSION"
+ cp "/tmp/clip_treated.$CLIP_EXTENSION" "/tmp/clip_temp.$CLIP_EXTENSION"
+ else
+ echo "something fucked up" && exit 1
+ fi
+
+
+ if [[ -n $(ffprobe -i "/tmp/clip_temp.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) && -n $(ffprobe -i "/tmp/clip_temp.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) ]]; then
+ ffmpeg -y -i "/tmp/join_temp.$CLIP_EXTENSION" -i "/tmp/clip_temp.$CLIP_EXTENSION" -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" "/tmp/join2.$CLIP_EXTENSION"
+ else
+ ffmpeg -y -i "/tmp/join_temp.$CLIP_EXTENSION" -i "/tmp/clip_temp.$CLIP_EXTENSION" -filter_complex "[0:v:0] [1:v:0]concat=n=2:v=1[outv]" -map "[outv]" "/tmp/join2.$CLIP_EXTENSION"
+ fi
+
+ mv "/tmp/join2.$CLIP_EXTENSION" "join.$CLIP_EXTENSION"
+ rm "/tmp/join_treated.$CLIP_EXTENSION" "/tmp/clip_treated.$CLIP_EXTENSION" "/tmp/join_temp.$CLIP_EXTENSION" "/tmp/clip_temp.$CLIP_EXTENSION"
+}
+
+join () {
+
+ if [[ "$REENCODE_FLAG" = "true" ]]; then
+ join_yes_reencode "$1"
+ elif [[ "$REENCODE_FLAG" = "false" ]]; then
+ join_no_reencode "$1"
+ else
+
+ while true; do
+ read -p "Are the video parameters identical (y/n)? " yn
+
+ case $( tr '[A-Z]' '[a-z]' <<< "$yn" ) in
+ y|yes) REENCODE_FLAG="false"; join_no_reencode "$1" ; break;;
+ n|no) REENCODE_FLAG="true"; join_yes_reencode "$1" ; break;;
+ *) echo "Invalid response." ;;
+ esac
+ done
+ fi
+}
+
+mute () {
+
+ ffmpeg -i "$1" -c copy -an "$CLIP_NAME [no audio].$CLIP_EXTENSION"
+
+}
+
+extract_audio () {
+
+ ffmpeg -i "$1" -codec:a libmp3lame "$CLIP_NAME [audio only].mp3"
+
+}
+
+scale () {
+
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT"
+ while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do
+
+ SCALE=${SCALE:-2}
+
+ re='^[0-9]*([.][0-9]+)?$'
+ if ! [[ $SCALE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ ffmpeg -i "$1" -vf scale="-1:$( printf "%.0f" $( bc <<< "scale=5; $CLIP_HEIGHT / $SCALE" ) )" -c:a copy "$CLIP_NAME [$SCALE].$CLIP_EXTENSION"
+
+}
+
+reverse () {
+
+ ffmpeg -i "$1" -vf reverse -af areverse "$CLIP_NAME [reversed].$CLIP_EXTENSION"
+
+}
+
+clip_to_gif () {
+
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT"
+ while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do
+
+ SCALE=${SCALE:-2}
+
+ re='^[0-9]*([.][0-9]+)?$'
+ if ! [[ $SCALE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ echo -e "-----CURRENT FRAMERATE OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_FRAMERATE fps"
+ while read -p "Enter new framerate [Default: 23.98; Recommended: $( bc <<< "scale=2; $CLIP_FRAMERATE / 2" )]: " RATE; do
+
+ RATE=${RATE:-23.98}
+
+ re='^[1-9][0-9]*([.][0-9]+)?$'
+ if ! [[ $RATE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ filters="fps=$RATE,scale=$( bc <<< "$CLIP_WIDTH / $SCALE" ):-1:flags=lanczos"
+ temp_palette="/tmp/_tmp_palette_$CLIP_NAME.png"
+ temp_gif="/tmp/tmp_out_$CLIP_NAME.gif"
+
+ ffmpeg -v warning -i "$1" -vf "$filters,palettegen=stats_mode=diff" -y $temp_palette
+ ffmpeg -i "$1" -i "$temp_palette" -lavfi "$filters,paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle" "$temp_gif"
+ gifsicle --optimize=3 --no-background --output "$CLIP_NAME [$SCALE - $RATE].gif" "$temp_gif"
+
+ rm "$temp_palette" "$temp_gif"
+
+}
+
+clip_to_webm () {
+
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT"
+ while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do
+
+ SCALE=${SCALE:-2}
+
+ re='^[1-9][0-9]*([.][0-9]+)?$'
+ if ! [[ $SCALE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ ffmpeg -i "$1" -c:v libvpx-vp9 -crf 30 -b:v 0 -b:a 128k -vf scale="-1:$( bc <<< "$CLIP_HEIGHT / $SCALE" )" -c:a libopus "$CLIP_NAME [$SCALE].webm"
+}
+
+webp_to_gif () {
+ python3 -c "from PIL import Image;Image.open('$1').save('${1%.webp}.gif','gif',save_all=True,optimize=True,background=0)"
+}
+
+FUNCTION=""
+
+while getopts "hicsrjmxgw" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-i get clip info] [-c crop clip] [-s scale/resize clip] [-r reverse clip] [-j join clips] [-m mute clip] [-x extract audio] [-g convert clip to gif] [-w convert clip to webm]"; exit ;;
+ i) FUNCTION="dump_clip_params"; break;;
+ c) FUNCTION="crop"; break ;;
+ s) FUNCTION="scale"; break ;;
+ r) FUNCTION="reverse"; break ;;
+ j) FUNCTION="join"; break ;;
+ m) FUNCTION="mute"; break ;;
+ x) FUNCTION="extract_audio"; break ;;
+ g) FUNCTION="convert"; break ;;
+ w) FUNCTION="clip_to_webm"; break ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+if [ "$#" -lt 2 ]; then
+ echo "Please insert only one flag and at least one argument"
+ exit
+else
+ echo -e "\c"
+fi
+
+i=0
+
+for file in "${@:2}"
+do
+ re='s|\.[^. ]*$||g'
+
+ ! [[ $file =~ $re ]] && echo "$file NOT A PROPER FILE, IGNORING..." && continue
+ get_clip_params "$file"
+ echo "$CLIP_NAME - $CLIP_EXTENSION - $FUNCTION"
+
+ if [[ "$FUNCTION" == "convert" ]]; then
+
+ if [[ "$CLIP_EXTENSION" == "gif" ]]; then
+ echo -e "$CLIP_NAME.$CLIP_EXTENSION IS ALREADY A GIF. SKIPPING...\n\n-------------------------$file DONE-------------------------\n\n" && continue
+ elif [[ "$CLIP_EXTENSION" == "webp" ]]; then
+ FUNCTION="webp_to_gif"
+ else
+ FUNCTION="clip_to_gif"
+ fi
+
+ fi
+
+ $FUNCTION "$file"
+ [[ "$FUNCTION" =~ ^(clip|webp)_to_gif$ ]] && FUNCTION="convert"
+ echo -e "\n\n------------------------- $file - DONE -------------------------\n\n"
+done
+
+
+# TO ADD: MORE FLEXIBLE FLAG/ARGUMENT PROCESSING, FLAG WHICH MAKES EVERY FUNCTION RESPECT FILEPATHS (I.E. DOESN'T DRAG EVERY OUTPUT TO CURRENT DIRECTORY)
+
+
diff --git a/clipboard_treater.sh b/clipboard_treater.sh
new file mode 100755
index 0000000..98689d4
--- /dev/null
+++ b/clipboard_treater.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+#dependencies: clipnotify
+
+function treater_daemon {
+ while clipnotify; do
+ CLIP="$(xclip -o -sel clip 2>&1)"
+ if ! [[ -f $( echo -n "$CLIP" | head -n 1 ) || -d $( echo -n "$CLIP" | head -n 1 ) || -n $( echo -n "$CLIP" | grep -i "Error: target STRING not available" ) || "$CLIP" -eq "" ]]; then
+ echo -n "$CLIP" | sed -r "s|$|\x0f|g" | tr '\n' ' ' | sed -r "s|-\x0f ||g; s|\x0f||g" | tr -s ' ' | xclip -sel clip
+ fi
+ done
+}
+
+daemon_flag=false;
+
+
+while getopts "hd" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-d toggle daemon]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ d) daemon_flag=true ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+
+if $daemon_flag ; then
+ if [[ $(pgrep -f "clipboard_treater.sh -d" | wc -l) -ge 3 ]]; then
+ $HOME/stuf/scripts/notification_wrapper.sh "$( pkill -o -f -e "clipboard_treater.sh -d" )" "CLIPTREAT"
+ else
+ treater_daemon </dev/null >/dev/null 2>&1 & disown
+ $HOME/stuf/scripts/notification_wrapper.sh "$(pgrep -f "clipboard_treater.sh -d" )" "CLIPTREAT"
+ fi
+ exit
+else
+ xclip -o -sel clip | sed -r "s|$|\x0f|g" | tr '\n' ' ' | sed -r "s|-\x0f ||g; s|\x0f||g" | tr -s ' ' | xclip -sel clip
+fi \ No newline at end of file
diff --git a/dependencies/GIMPsRGB.icc b/dependencies/GIMPsRGB.icc
new file mode 100644
index 0000000..b1d4416
--- /dev/null
+++ b/dependencies/GIMPsRGB.icc
Binary files differ
diff --git a/dependencies/JapanColor2011Coated.icc b/dependencies/JapanColor2011Coated.icc
new file mode 100644
index 0000000..58354e8
--- /dev/null
+++ b/dependencies/JapanColor2011Coated.icc
Binary files differ
diff --git a/dl_helper.sh b/dl_helper.sh
new file mode 100755
index 0000000..e6a5fe1
--- /dev/null
+++ b/dl_helper.sh
@@ -0,0 +1,103 @@
+#!/bin/bash
+# set -e
+
+### REQUIREMENTS
+# gallery-dl
+# yt-dlp
+# mloader (pip or AUR)
+
+
+file_browser="thunar"
+LINK="$1"
+DIRFLAG="$2"
+# 0 = do nothing, 1 = store location in dirlist, 2 = open dirlist, 3 = 2+1
+TYPE=""
+
+# everything below this comment is horrible
+[[ "$LINK" == "2" ]] && DIRFLAG=2 && LINK="show_files"
+[[ ! "$DIRFLAG" =~ ^[0-3]$ ]] && DIRFLAG=3
+[[ -z "$LINK" || -z "$DIRFLAG" ]] && echo "ERROR: NOT ENOUGH ARGUMENTS" && exit 1
+
+declare -A TYPES=( ["weebcentral\.com"]="manga" ["mangadex\.org"]="manga" ["youtube\.com\/@.*\/videos"]="yt_playlist" ["youtube\.com\/playlist\?list"]="yt_playlist" ["youtube\.com"]="yt_clip" ["pixiv\.net"]="art" ["skeb\.jp"]="art" ["(danbooru\.)?donmai\.us"]="art" ["deviantart\.com"]="art" ["kemono\.su"]="art" ["twitter\.com"]="art" ["x\.com"]="art" ["bsky\.app"]="art" ["baraag\.net"]="art" ["nhentai\.net"]="ecchi" ["e.hentai\.org"]="ecchi" )
+declare -A LOCATIONS=( ["show_files"]="show_files" ["manga"]="$HOME/Downloads/Read or Die" ["art"]="$HOME/Tempsktop/gallery-dl" ["yt_clip"]="$HOME/Downloads/yt-dlp" ["yt_playlist"]="$HOME/Downloads/yt-dlp" ["ecchi"]="$HOME/Tempsktop/gallery-dl")
+
+if [[ "$LINK" == "show_files" ]]; then
+ TYPE="$LINK"
+else
+ longest_match=""
+ for expr in "${!TYPES[@]}"; do
+ [[ $LINK =~ ^http(s)?\:\/\/(www\.)?${expr}.*$ && ${#expr} -gt ${#longest_match} ]] && longest_match="$expr" && TYPE="${TYPES[$longest_match]}"
+ done
+fi
+
+[[ -z "$TYPE" ]] && echo "ERROR: LINK NOT PROPERLY PARSED" && exit 1
+
+LOCATION="${LOCATIONS[$TYPE]}"
+echo "LINK : $LINK, TYPE: $TYPE, LOCATION: $LOCATION"
+
+case "$TYPE" in
+
+"manga")
+ MANGA="$( gallery-dl `curl -Ls -o /dev/null -w %{url_effective} $LINK` --destination "$LOCATION" --ugoira-conv 2>&1 )"
+ # echo $?
+ # echo $MANGA
+ if [[ $? -ne 0 ]]; then
+ MANGA_URL="$( echo $MANGA | grep -o -E "https://mangaplus\.shueisha\.co\.jp/viewer/[0-9]+" )"
+ echo $MANGA_URL
+ [[ -n "$MANGA_URL" ]] && mloader --out "$LOCATION/mangaplus" --raw --chapter-title --chapter-subdir `curl -Ls -o /dev/null -w %{url_effective} $MANGA_URL`
+ fi
+ ;;
+
+"art")
+ # gallery-dl `curl -Ls -o /dev/null -w %{url_effective} $LINK` --directory "$LOCATION" --ugoira-conv
+ gallery-dl `curl -Ls -o /dev/null -w %{url_effective} $LINK` --directory "$LOCATION" --ugoira-conv -o extractor.twitter.tweet-endpoint=restid -o extractor.twitter.conversations=false
+ ;;
+
+"yt_clip")
+ yt-dlp `curl -Ls -o /dev/null -w %{url_effective} $LINK` --paths "$LOCATION" --no-playlist
+ ;;
+"yt_playlist")
+ title="$(yt-dlp `curl -Ls -o /dev/null -w %{url_effective} $LINK` --skip-download --flat-playlist --dump-single-json --no-warnings | jq -r .title)"
+ mkdir -p ${LOCATION}/${title} || { echo "ERROR: FAILED TO CREATE PLAYLIST SUBFOLDER" ; exit -1 ; }
+ yt-dlp `curl -Ls -o /dev/null -w %{url_effective} $LINK` --paths "${LOCATION}/${title}" -o "%(playlist_index)s. %(title)s.%(ext)s"
+ ;;
+
+"ecchi")
+ # gallery-dl `curl -Ls -o /dev/null -w %{url_effective} $LINK` --destination "$LOCATION"
+ gallery-dl `curl -Ls -o /dev/null -w %{url_effective} $LINK` --destination "$LOCATION" -o extractor.twitter.tweet-endpoint=restid -o extractor.twitter.conversations=false
+ for file in ${LOCATION}/nhentai/*/*webp; do newfile="${file//nhentai_*_0/}" ; newfile="${newfile%%webp}png" ; magick "$file" "$newfile" && rm -f "$file" ; done
+ for file in ${LOCATION}/exhentai/*/*webp; do newfile="${file//[0-9][0-9][0-9][0-9][0-9][0-9][0-9]_*_*_/}" ; newfile="${newfile%%webp}png" ; magick "$file" "$newfile" && rm -f "$file" ; done
+ ;;
+"show_files")
+ ;;
+
+*)
+ echo -n "ERROR: UNKNOWN TYPE"
+ exit 1
+ ;;
+
+esac
+
+[[ "$DIRFLAG" -eq 0 ]] && exit
+
+[[ "$DIRFLAG" -eq 1 || "$DIRFLAG" -eq 3 ]] && echo "$LOCATION" >> "/tmp/dirlist.txt"
+
+if [[ "$DIRFLAG" -eq 2 || "$DIRFLAG" -eq 3 ]]; then
+
+ dirs=$( cat "/tmp/dirlist.txt" | sort | uniq )
+ # "$file_browser"
+
+ IFS=$'\n'
+ for dir in $dirs; do
+
+ "$file_browser" "$dir"
+
+ # wid=( $( xdotool search --desktop $( xdotool get_desktop ) --class "$file_browser" ) )
+ # lastwid=${wid[*]: -1}
+ # [[ -n "$lastwid" ]] && xdotool windowactivate --sync "$lastwid" key ctrl+t ctrl+l && xdotool type -delay 0 "$dir" && xdotool key Return || echo "ERROR: UNABLE TO OPEN FILE BROWSER"
+
+ done
+ unset IFS
+
+ rm -f "/tmp/dirlist.txt"
+fi
diff --git a/dlanor_critic.sh b/dlanor_critic.sh
new file mode 100755
index 0000000..f0835cb
--- /dev/null
+++ b/dlanor_critic.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+BASE=$1
+BASENAME=$( basename "$BASE" )
+CRITIC="$HOME/Mozda/Jwebmsounds/dlanor_critic.mkv"
+DIMENSIONS=$( ffprobe -i "$CRITIC" 2>&1 | grep -o "[0-9]\+x[0-9]\+" )
+
+convert "$BASE" -resize "$DIMENSIONS"\! "/tmp/resized_$BASENAME"
+ffmpeg -i "/tmp/resized_$BASENAME" -i "$CRITIC" -filter_complex '[1:v]colorkey=0x00fe00:0.3:0.2[ckout];[0:v][ckout]overlay[out]' -map '[out]' -map 1:a -c:a copy "$HOME/Desktop/critic.mp4"
+
+rm -f "/tmp/resized_$BASENAME" \ No newline at end of file
diff --git a/ffmgrep.sh b/ffmgrep.sh
new file mode 100755
index 0000000..243c0c7
--- /dev/null
+++ b/ffmgrep.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# LOOKS THROUGH SUBTITLES OF GIVEN FILES TO FIND THE TIMESTAMP(S) OF INPUT STRING
+
+if (($# < 2)); then
+ echo >&2 "$0: script requires at least two arguments: Text to grep and at least one file with subtitles, in that order"
+ exit 1
+else
+ expr="$1"
+ shift
+fi
+
+FILES=()
+TMPDIR="ffmgrep.XXXXX"
+STREAM=0
+CACHEFLAG=0
+COLOR1="\o033[32m"
+COLOR2="\o033[36m"
+ENDCOLOR="\o033[0m"
+
+while (($#)); do
+ if [[ ! -f $1 ]]; then
+ echo >&2 "$0: $1 is not a valid file"
+ exit 1
+ else
+ FILES+=("$1")
+ fi
+ shift
+done
+
+# we do a little caching
+
+OLD_TMPDIR=$( ls -ltc /tmp/ | grep -E "^d.*ffmgrep\..{5}" | head -n1 | sed -r "s|.*(ffmgrep)|\/tmp\/\1|g" )
+
+if [[ ! -d "$OLD_TMPDIR" ]]; then
+ TMPDIR=$(mktemp --directory -t "$TMPDIR")
+else
+ for file in "${FILES[@]}"; do
+ basefile="$(basename "$file")"
+ if [[ ! -f "${OLD_TMPDIR}/${basefile%.*}.vtt" ]]; then
+ rm -r $OLD_TMPDIR
+ TMPDIR=$(mktemp --directory -t "$TMPDIR")
+ break
+ fi
+ done
+ [[ -d "$OLD_TMPDIR" ]] && TMPDIR="$OLD_TMPDIR" && CACHEFLAG=1
+fi
+
+for file in "${FILES[@]}"; do
+ [[ $CACHEFLAG -eq 1 ]] && break
+ basefile="$(basename "$file")"
+ [[ -z $(ffmpeg -i "$file" -c copy -map 0:s:0 -frames:s 1 -f null - -v 0 -hide_banner; echo $?) ]] && echo "NO SUBTITLES FOUND IN FILE $file. SKIPPING..." && continue
+ ffmpeg -i "$file" -map "0:s:${STREAM}" -hide_banner -loglevel error -f webvtt "${TMPDIR}/${basefile%.*}.vtt"
+done
+
+# grep -B 1 --no-group-separator -E -i "$expr" ${TMPDIR}/*vtt | awk 'NR%2{printf "%s ",$0;next;}1' | sed -r "s|${TMPDIR//\//\\\/}\/(.*?)\.vtt-([[:digit:]]+:[[:digit:]]+\.[[:digit:]]+ --> [[:digit:]]+:[[:digit:]]+\.[[:digit:]]+) .*?\.vtt:(.*$)|${COLOR1}\1${ENDCOLOR}: ${COLOR2}(\2)${ENDCOLOR} \3|g"
+
+grep --with-filename -r "" ${TMPDIR} | sed -r "s|.*\.vtt:$||g" | awk -v RS= '{$1=$1}1' | grep --no-group-separator -E -i "$expr" | sed -r "s|${TMPDIR//\//\\\/}\/[^\/]*\.vtt[-:]||2g; s|${TMPDIR//\//\\\/}\/(.*?)\.vtt[-:](([[:digit:]]+[:\.])+[[:digit:]]+ --> ([[:digit:]]+[:\.])+[[:digit:]]+) (.*$)|${COLOR1}\1${ENDCOLOR}: ${COLOR2}(\2)${ENDCOLOR} \5 |g"
+
diff --git a/firewallsetup/README.md b/firewallsetup/README.md
new file mode 100644
index 0000000..58ed5b4
--- /dev/null
+++ b/firewallsetup/README.md
@@ -0,0 +1,28 @@
+# firewallsetup
+## Fast Firewall Setup
+
+This is a script for setting up a firewall with settings for tarpitting ssh and basic protections that everyone needs.
+
+## Download the rules to /etc/
+```
+git clone https://github.com/ChrisTitusTech/firewallsetup.git
+````
+## Make the Rules Permenant
+### Debian-based Distributions
+```
+sudo apt install iptables-persistent
+sudo /etc/init.d/netfilter-persistent save
+```
+### Arch Linux Distributions
+*Use iptable-save which is pre-installed*
+```
+sudo iptables-save > /etc/iptables/iptables.rules
+```
+### RHEL / CentOS Distributions
+*This is by far the simpliest way to save rules and check them # chkconfig --list | grep iptables*
+
+*Note: chkconfig iptables on only needs to be run once to turn the service on*
+```
+sudo chkconfig iptables on
+sudo service iptables save
+```
diff --git a/firewallsetup/firewall b/firewallsetup/firewall
new file mode 100644
index 0000000..b047a19
--- /dev/null
+++ b/firewallsetup/firewall
@@ -0,0 +1,67 @@
+#!/bin/bash
+#
+# iptables example configuration script
+
+# Drop ICMP echo-request messages sent to broadcast or multicast addresses
+echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
+
+# Drop source routed packets
+echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
+
+# Enable TCP SYN cookie protection from SYN floods
+echo 1 > /proc/sys/net/ipv4/tcp_syncookies
+
+# Don't accept ICMP redirect messages
+echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
+
+# Don't send ICMP redirect messages
+echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
+
+# Enable source address spoofing protection
+echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
+
+# Log packets with impossible source addresses
+echo 1 > /proc/sys/net/ipv4/conf/all/log_martians
+
+# Flush all chains
+/sbin/iptables --flush
+
+# Allow unlimited traffic on the loopback interface
+/sbin/iptables -A INPUT -i lo -j ACCEPT
+/sbin/iptables -A OUTPUT -o lo -j ACCEPT
+
+# Set default policies
+/sbin/iptables --policy INPUT DROP
+/sbin/iptables --policy OUTPUT DROP
+/sbin/iptables --policy FORWARD DROP
+
+# Previously initiated and accepted exchanges bypass rule checking
+# Allow unlimited outbound traffic
+/sbin/iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
+/sbin/iptables -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
+
+#Ratelimit SSH for attack protection
+/sbin/iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
+/sbin/iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
+/sbin/iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
+
+# Allow certain ports to be accessible from the outside
+/sbin/iptables -A INPUT -p tcp --dport 25565 -m state --state NEW -j ACCEPT #Minecraft
+/sbin/iptables -A INPUT -p tcp --dport 8123 -m state --state NEW -j ACCEPT #Dynmap plugin
+
+# Other rules for future use if needed. Uncomment to activate
+# /sbin/iptables -A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT # http
+# /sbin/iptables -A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT # https
+
+# UDP packet rule. This is just a random udp packet rule as an example only
+# /sbin/iptables -A INPUT -p udp --dport 5021 -m state --state NEW -j ACCEPT
+
+# Allow pinging of your server
+/sbin/iptables -A INPUT -p icmp --icmp-type 8 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
+
+
+# Drop all other traffic
+/sbin/iptables -A INPUT -j DROP
+
+# print the activated rules to the console when script is completed
+/sbin/iptables -nL
diff --git a/firewallsetup/firewall-down b/firewallsetup/firewall-down
new file mode 100644
index 0000000..23a74cb
--- /dev/null
+++ b/firewallsetup/firewall-down
@@ -0,0 +1,15 @@
+#!/bin/bash
+/sbin/iptables -F
+/sbin/iptables -X
+/sbin/iptables -t nat -F
+/sbin/iptables -t nat -X
+/sbin/iptables -t mangle -F
+/sbin/iptables -t mangle -X
+
+# the rules allow us to reconnect by opening up all traffic.
+/sbin/iptables -P INPUT ACCEPT
+/sbin/iptables -P FORWARD ACCEPT
+/sbin/iptables -P OUTPUT ACCEPT
+
+# print out all rules to the console after running this file.
+/sbin/iptables -nL
diff --git a/firewallsetup/firewall-reload b/firewallsetup/firewall-reload
new file mode 100644
index 0000000..948123a
--- /dev/null
+++ b/firewallsetup/firewall-reload
@@ -0,0 +1,3 @@
+#!/bin/bash
+/etc/firewallsetup/firewall-down
+/etc/firewallsetup/firewall
diff --git a/hackshot.sh b/hackshot.sh
new file mode 100755
index 0000000..8bd6e69
--- /dev/null
+++ b/hackshot.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+
+PREVIEWBORDERCOLOR="#c8bcb7"
+PREVIEWBORDERTHICKNESS="2"
+ROFI_THEME="BernGray"
+SCROTDIR="$HOME/Desktop"
+SCROTPROGRAM="gimp"
+SCROTTMP="$( mktemp -t hackshot_XXXXXXXXXX.png )"
+SCROTFINAL="Screenshot_$( date -u +%Y-%m-%d_%H-%M-%S ).png"
+SCROTMONITOR="$( xdotool getmouselocation --shell | grep "SCREEN=" | cut -d "=" -f 2 )"
+SCROTGEOMETRY=""
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '--------------------------------------------------------------------------------------------'
+ 'Take screenshot of selected region | hackshot.sh -r | PrintScr'
+ 'Take screenshot of active window | hackshot.sh -w | Ctrl+PrintScr'
+ 'Take screenshot of active screen | hackshot.sh -s | Shift+PrintScr'
+ 'Take screenshot of all screens | hackshot.sh | Ctrl+Shift+PrintScr'
+)
+
+while getopts "hrws" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-d toggle daemon] [-s select]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ r) SCROTGEOMETRY="-g $( hacksaw -c "$PREVIEWBORDERCOLOR" 2>&1 )" ;;
+ w) SCROTGEOMETRY="-g $( xdotool getwindowgeometry $(xdotool getactivewindow) | grep -o -E "[0-9]+(,|x)[0-9]+" | tac | sed -r "s|([0-9]+),([0-9]+)|+\1+\2|g" | tr -d "\n" )" ;;
+ # w) SCROTGEOMETRY="-i $( xdotool getactivewindow )" ;; DOESN'T JIBE BECAUSE OF THE RESOLUTION CHECK BELOW
+ s) SCROTGEOMETRY="--single-screen" ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+[[ -n $( echo "$SCROTGEOMETRY" | grep "Error" ) ]] && exit
+
+rm /tmp/hackshot_*png
+
+if [[ -z "$SCROTGEOMETRY" ]]; then
+ shotgun "${SCROTTMP}"
+else
+ shotgun $SCROTGEOMETRY "${SCROTTMP}"
+fi
+
+convert -bordercolor "$PREVIEWBORDERCOLOR" -border "$PREVIEWBORDERTHICKNESS" "${SCROTTMP}" "/tmp/hackshot_preview.png"
+
+# BREAKS WITH MULTIPLE MONITORS BECAUSE IT GETS THE COMBINED DIMENSIONS OF ALL OF THEM
+
+# PREVIEWWIDTH="$(( $( xdpyinfo | grep -i -A 5 "screen #$SCROTMONITOR" | awk '/dimensions/{print $2}' | cut -d "x" -f 1 ) / 3 * 2 ))"
+# PREVIEWHEIGHT="$(( $( xdpyinfo | grep -i -A 5 "screen #$SCROTMONITOR" | awk '/dimensions/{print $2}' | cut -d "x" -f 2 ) / 3 * 2 ))"
+
+PREVIEWWIDTH="$(( $( xdotool getdisplaygeometry | cut -d " " -f 1 ) / 3 * 2 ))"
+PREVIEWHEIGHT="$(( $( xdotool getdisplaygeometry | cut -d " " -f 2 ) / 3 * 2 ))"
+
+if [[ "$SCROTGEOMETRY" != "--single-screen" ]]; then
+
+ SCROTWIDTH="${SCROTGEOMETRY#* }"; SCROTWIDTH="${SCROTWIDTH%x*}";
+ SCROTHEIGHT="${SCROTGEOMETRY#*x}"; SCROTHEIGHT="${SCROTHEIGHT%%+*}";
+
+ [[ "$SCROTWIDTH" -lt "$PREVIEWWIDTH" ]] && PREVIEWWIDTH="$SCROTWIDTH"
+ [[ "$SCROTHEIGHT" -lt "$PREVIEWHEIGHT" ]] && PREVIEWHEIGHT="$SCROTHEIGHT"
+
+fi
+
+PREVIEWWIDTH="$(( "$PREVIEWWIDTH" + ( "$PREVIEWBORDERTHICKNESS" * 2 ) ))"
+PREVIEWHEIGHT="$(( "$PREVIEWHEIGHT" + ( "$PREVIEWBORDERTHICKNESS" * 2 ) ))"
+
+# SAME AS ABOVE
+
+# PREVIEWX="$(( ( $( xdpyinfo | grep -i -A 5 "screen #$SCROTMONITOR" | awk '/dimensions/{print $2}' | cut -d "x" -f 1 ) / 2 ) - ( ${PREVIEWWIDTH} / 2 ) ))"
+# PREVIEWY="$(( ( $( xdpyinfo | grep -i -A 5 "screen #$SCROTMONITOR" | awk '/dimensions/{print $2}' | cut -d "x" -f 2 ) / 2 ) - ( ${PREVIEWHEIGHT} / 2 ) ))"
+
+PREVIEWX="$(( ( $( xdotool getdisplaygeometry | cut -d " " -f 1 ) / 2 ) - ( ${PREVIEWWIDTH} / 2 ) ))"
+PREVIEWX="$(( ( $( xdotool getdisplaygeometry | cut -d " " -f 2 ) / 2 ) - ( ${PREVIEWWIDTH} / 2 ) ))"
+
+feh -x -g "${PREVIEWWIDTH}x${PREVIEWHEIGHT}+${PREVIEWX}+${PREVIEWY}" -. "/tmp/hackshot_preview.png" &
+
+CHOICE=$(echo -e "1. Save to $SCROTDIR\n2. Copy to clipboard\n3. Open with $SCROTPROGRAM\n4. Abort" \
+| rofi -dmenu -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 3 -no-click-to-exit )
+
+kill $!
+
+if [[ -n $( grep "^1" <<< "$CHOICE" ) ]]; then
+ mv "${SCROTTMP}" "${SCROTDIR}/${SCROTFINAL}"
+elif [[ -n $( grep "^2" <<< "$CHOICE" ) ]]; then
+ echo "file:///${SCROTTMP}" | xclip -selection clipboard -t text/uri-list
+elif [[ -n $( grep "^3" <<< "$CHOICE" ) ]]; then
+ nohup "$SCROTPROGRAM" "${SCROTTMP}" >/dev/null 2>&1 &
+else
+ # rm "${SCROTTMP}"
+ exit 0
+fi
diff --git a/hamachi_direct_fisher.sh b/hamachi_direct_fisher.sh
new file mode 100755
index 0000000..c8e1412
--- /dev/null
+++ b/hamachi_direct_fisher.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+while true; do
+ HOST=$( hamachi list | grep "\[.*\]" | sed -r 's|^.*owner\: ||g; s|[\(\)]||g' | awk '{print $2, " ", $1}' )
+ [[ -n "$HOST" ]] && break
+ hamachi login
+ sleep 5
+done
+
+echo "Attempting to establish direct connection with: $HOST"
+
+while true; do
+
+ CONNECTION=$( hamachi list | grep "$HOST" | sed -r "s|^.*(\ ){25}||g; s|(\ ){2,3}.*||g" )
+ echo "Connection: $CONNECTION"
+
+ [[ "$CONNECTION" = "via server" ]] && sleep 5 && continue
+
+ [[ "$CONNECTION" = "direct" ]] && break || ( hamachi logout && sleep 5 && hamachi login && sleep 5 )
+
+done \ No newline at end of file
diff --git a/iedupes.sh b/iedupes.sh
new file mode 100755
index 0000000..f70e593
--- /dev/null
+++ b/iedupes.sh
@@ -0,0 +1,135 @@
+#!/bin/bash
+
+
+# trap ctrl-c and call ctrl_c()
+
+function ctrl_c() {
+ echo "ABORTING DUPE REVIEW"
+ cat "$exceptionsfile" | (read -r; printf "%s\n" "$REPLY"; sort -u) > "${sorted_exceptionsfile}"
+ mv -f "${sorted_exceptionsfile}" "$exceptionsfile"
+ [[ -f "${optimized_exceptionsfile}" ]] && rm -f "${optimized_exceptionsfile}"
+ exit -1
+}
+
+trap ctrl_c SIGINT SIGTERM
+
+
+IE="$(pwd)"
+exceptionsfile="${IE}/exceptions.txt"
+optimized_exceptionsfile=""
+sorted_exceptionsfile="$( mktemp /tmp/sorted_exceptions_XXXXXXXX.txt )"
+
+optimization_period="7"
+optimized_curr="$(( $(date +"%s") / 86400 ))"
+skip_flag=false
+DEFAULT_SIMILARITY="Small"
+
+while getopts "hy" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-y automatically add exceptions]"; exit ;;
+ y) skip_flag=true ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+( [ -e "$exceptionsfile" ] || (touch "$exceptionsfile" && echo "##### LAST TIMESTAMP OF EXCEPTION FILE OPTIMIZATION (IN DAYS SINCE UNIX EPOCH): ${optimized_curr} #####" > "$exceptionsfile") ) && [ ! -w "$exceptionsfile" ] && echo "ERROR: Cannot write to $exceptionsfile" && exit 1
+
+optimized_last="$(cat "$exceptionsfile" | head -n 1 | grep -E "##### LAST TIMESTAMP OF EXCEPTION FILE.*#####" | grep -oE "[0-9]+")"
+[ -z "$optimized_last" ] && echo "ERROR: Cannot parse last optimization date of $exceptionsfile" && exit 1
+
+[[ -n $(find "$(pwd)" -regextype awk -iregex ".*[\[\]\(\)\{\}\*\+\?]+.*") ]] && echo -e "REGEX SYMBOLS FOUND IN FILENAMES, IE IN SHAMBLES. ABORTING.\n" && exit -1
+
+function optimize_exceptions {
+
+ optimized_exceptionsfile="$( mktemp /tmp/optimized_exceptions_XXXXXXXX.txt )"
+
+ optimized_date_rel=$(( optimized_curr - optimized_last ))
+ [[ $optimized_date_rel -ge $optimization_period ]] || return 1
+
+ echo "##### LAST TIMESTAMP OF EXCEPTION FILE OPTIMIZATION (IN DAYS SINCE UNIX EPOCH): ${optimized_curr} #####" > "${optimized_exceptionsfile}"
+
+ while read line; do
+ lineexpr=$(echo -e "$line" | sed -r "s| \/|\.*\/|g; s|^|\.*|g; s|$|\.*|g")
+ [[ -n $( grep -E "##### LAST TIMESTAMP OF EXCEPTION FILE.*#####" <<< "$line" ) || $(grep -a "$lineexpr" exceptions.txt | wc -l) -ge 2 ]] && continue || echo "$line" >> "${optimized_exceptionsfile}"
+ done < "$exceptionsfile"
+
+ exceptions_lines=$(wc -l < "$exceptionsfile")
+ optimized_exceptions_lines=$(wc -l < "${optimized_exceptionsfile}")
+ optimization_diff=$(( exceptions_lines - optimized_exceptions_lines ))
+ [[ $optimization_diff -gt 0 ]] || return 1
+
+ while true; do
+ read -p "Exceptions file (${exceptionsfile}) has been optimized (${optimization_diff} new optimizations, ${optimized_date_rel} days since last optimization). Would you like to overwrite the existing exceptions file (y/n)? " yn
+
+ case $( tr '[A-Z]' '[a-z]' <<< "$yn" ) in
+ y|yes) rm "$exceptionsfile" && cp "${optimized_exceptionsfile}" "$exceptionsfile" && echo "Exceptions file updated." ; break;;
+ n|no) echo "Update aborted." ; break;;
+ *) echo "Invalid response." ;;
+ esac
+ done
+
+ rm -f "${optimized_exceptionsfile}"
+}
+
+$skip_flag || optimize_exceptions
+
+re='^Minimal|VerySmall|Small|Medium|High|VeryHigh$'
+
+declare SIMILARITY
+
+if $skip_flag ; then
+ SIMILARITY="$DEFAULT_SIMILARITY"
+else
+ while read -p "Enter similarity preset [Default: Small; Allowed: Minimal, VerySmall, Small, Medium, High, VeryHigh]: " SIMILARITY; do
+
+ SIMILARITY=${SIMILARITY:-"$DEFAULT_SIMILARITY"}
+
+ if ! [[ $SIMILARITY =~ $re ]]; then
+ echo "error: Invalid input" >&2; continue
+ fi
+
+ break
+ done
+fi
+
+
+mapfile -t matches < <( czkawka-cli image -d "$IE" -s "$SIMILARITY" | sed -r "s| \- [0-9]*x[0-9]* \- [0-9]*(\.[0-9]*)? [KMGTP]iB \- [a-zA-Z ]*$||g ; s|[0-9]+ images which have similar friends||g ; s|\"||g ; s|^Found ||g ; s|.*-------MESSAGES-------.*||g ; s|.*Properly loaded.*||g ; s|.*Properly saved to file.*||g " \
+| head -n -5 | awk -v RS= '{$1=$1; t=!/\//; if(NR>1 && t) print ""; print; if(t) print ""}' )
+
+
+count=0
+
+for match in "${matches[@]}"
+do
+ matches[$count]=$( echo "$match" | sed -r "s|\ \/|\n\/|g" | sort | tr '\n' ' ' | sed -r "s| $|\n|" )
+ ((count++))
+done
+
+
+printf '%s\n' "${matches[@]}" | while IFS= read -r line
+do
+ lineexpr=$(echo -e "$line" | sed -r "s| \/|\.*\/|g; s|^|\.*|g; s|$|\.*|g")
+ [[ -n $(grep -a -E "$lineexpr" "$exceptionsfile") ]] && continue || echo "$lineexpr"
+ if $skip_flag ; then
+ echo "$line" >> "$exceptionsfile"
+ else
+ echo -e "$line" | sed -r "s|\ \/home\/|\n\/home\/|g" | /bin/feh --info "printf '%S %wx%h'" --zoom max --scale-down -g 1280x720 -B black -d --action1 "gio trash %F" --action2 "cat %L | sed -r 's|^\.|$(pwd)|g' | sort | tr '\n' ' ' | paste -s -d ' ' | sed 's| $||g' >> \"$exceptionsfile\"" --action3 "$HOME/stuf/scripts/scriptlets/feh_action3.sh %F %L" -f -
+ fi
+done
+
+cat "$exceptionsfile" | (read -r; printf "%s\n" "$REPLY"; sort -u) > "${sorted_exceptionsfile}"
+mv -f "${sorted_exceptionsfile}" "$exceptionsfile"
+
+# EVERYTHING IS HOW IT'S SUPPOSED TO BE, THERE CAN BE NO IMPROVEMENTS, NOTHING ELSE WORKED
+# | sed -e "s|\(\/.*\)|\"\1\"|g"
+# ; s|\ |\\\ |g
+
+#THIS IS BEST PRACTICE FOR ENVIRONMENT VARIABLES, CONSIDER CORRECTING ~/stuf STUFF
+# -t 95% WORKS FOR ELIMINATING THE CHAFF, BUT IT MIGHT MISS SOME
+
+#feh . --action1 "cat %L | sed -r 's|^\.|$(pwd)|g' | sort | tr '\n' ' ' "
+#czkawka_cli image -d "/home/jay/Tempsktop" -s "High" | sed -r "s| \- [0-9]*x[0-9]* \- [0-9]*\.[0-9]* [KMGTP]iB \- [a-zA-Z ]*$||g ; s|Found [0-9]+ images which have similar friends||g " | head -n -5 | awk -v RS= '{$1=$1; t=!/\//; if(NR>1 && t) print ""; print; if(t) print ""}' | xargs -n 1| sort | xargs
+
+
+# IF IT WORKS WITH QUOTES, ADD QUOTES
+
diff --git a/keymap_toggler.sh b/keymap_toggler.sh
new file mode 100755
index 0000000..e83f64e
--- /dev/null
+++ b/keymap_toggler.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+PRESET="$1"
+DEVICE="Microsoft X-Box One S pad"
+
+if [[ $(ps aux | grep -v grep | grep "input-remapper") ]]
+then
+ if [[ $(ps -aux | grep -v grep | grep "\[input-remapper-\]") ]]
+ then
+ input-remapper-control --command start --device "$DEVICE" --preset "$PRESET"
+ $HOME/stuf/scripts/notification_wrapper.sh "\nController mapping ON" "KEYTOG"
+ else
+ input-remapper-control --command stop-all --device "$DEVICE"
+ $HOME/stuf/scripts/notification_wrapper.sh "\nController mapping OFF" "KEYTOG"
+ fi
+
+else
+ nohup input-remapper-service >/dev/null 2>&1 &
+
+ # while [[ ! $(ps -aux | grep -v grep | grep "\[input-remapper-\]") ]]; do
+ # input-remapper-control --command start --device "$DEVICE" --preset "$PRESET"
+ # input-remapper-control --command stop-all --device "$DEVICE"
+ # done
+
+ $HOME/stuf/scripts/notification_wrapper.sh "\ninput-remapper-service started" "KEYTOG"
+ #don't ask
+fi
+
+
+#IMPLEMENT KILLALL
+
+#WHEN YOU FIX THE RIGHT TRIGGER, MOVE F TO IT, AND MAKE BOTTOM BUTTON ESC
diff --git a/learning_alarm.sh b/learning_alarm.sh
new file mode 100644
index 0000000..6849cd9
--- /dev/null
+++ b/learning_alarm.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+function alarm_daemon {
+ while true; do
+ # NOISE
+ sleep $(($RANDOM % 1200 + 1500))
+ done
+}
+
+
+if [[ $(pgrep -f "learning_alarm.sh" | wc -l) -ge 3 ]]; then
+ $HOME/stuf/scripts/notification_wrapper.sh "$( pkill -o -f -e "learning_alarm.sh" )" "RANDALRM"
+else
+ alarm_daemon </dev/null >/dev/null 2>&1 & disown
+ $HOME/stuf/scripts/notification_wrapper.sh "$(pgrep -f "learning_alarm.sh" )" "RANDALRM"
+fi \ No newline at end of file
diff --git a/minivac.sh b/minivac.sh
new file mode 100755
index 0000000..6fb7fa7
--- /dev/null
+++ b/minivac.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+
+
+######################### VARIABLES #########################
+
+
+NAME="FLIGU-GIGU"
+
+
+MICROPHONE="alsa_input.pci-0000_00_1b.0.analog-stereo"
+SPEAKERS="alsa_output.pci-0000_00_1b.0.analog-stereo"
+PROGRAM="WEBRTC VoiceEngine"
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '------------------------------------------------------------------------------------------'
+ 'Change Sink | minivac.sh | Ctrl+Shift+F7'
+ 'Load Sinks | minivac.sh -l | -'
+ 'Unload Sinks | minivac.sh -u | -'
+ 'Notify-send Sinks | minivac.sh -n | F7'
+ 'Reset Sinks | minivac.sh -r | Shift+F7'
+)
+
+DMENU_APPEARANCE="mononoki;11;#191919;#AAAAAA;#890089;#FFFFFF;Select Input:"
+
+for i in {1..7}
+do
+ declare "dm$i=$( cut -f$i -d';' <<< "$DMENU_APPEARANCE" )" # CONVERT DMENU APPEARANCE STRIP INTO SEPARATE PARAMETERS
+done
+
+mapfile -t INPUTS < <( pactl list sink-inputs | grep -B 30 -A 10 "application.process.binary" | grep "Sink Input\|application\.name\|media\.name" | grep -B 2 "application\.name" | sed -r "/--/d; s|Sink\ Input\ ||g; s|[[:space:]]*application\.name\ \=|\~|g; s|[[:space:]]*media\.name\ \=\ |\~\ |g" | paste -sd ' \n' )
+
+mapfile -t SINKS < <( pactl list sinks | grep -A 3 "Sink #" | grep "Sink #\|Name:\|Description:" | sed -r "s|[[:space:]]*Name:\ |\~ |g; s|[[:space:]]*Description:\ |\~\ |g" | paste -sd ' \n' )
+
+######################### HELPER FUNCTIONS #########################
+
+treat_inputs () {
+ i=0
+ for INPUT in "${INPUTS[@]}"
+ do
+ SINK=$( pactl list sink-inputs | grep -A 5 "$( sed -r "s| .*||g" <<< "$INPUT" )" | grep "Sink:")
+ SINK_TRANSLATED=$( pactl list sinks | grep -A 3 "$( sed -r 's|: | #|g' <<< "$SINK" )" | grep "Description" | sed -r "s|.*Description: ||g" )
+ INPUTS[i]="$INPUT -> $SINK_TRANSLATED"
+ ((i++))
+ done
+}
+
+load_sinks () {
+ if [[ -z "$( pw-cli list-objects | grep -i "node.name = \"$NAME\"")" ]]; then
+ pactl load-module module-null-sink media.class=Audio/Sink sink_name="$NAME" channel_map=stereo
+ pactl load-module module-null-sink media.class=Audio/Source/Virtual sink_name="$NAME-MIC" channel_map=front-left,front-right
+ connect_nodes
+ else
+ echo -e "\nSink $NAME already loaded."
+ fi
+}
+
+connect_nodes () {
+ if [[ ! -z "$( pw-cli list-objects | grep -i "node.name = \"$PROGRAM\"")" ]]; then
+ pw-link "mpv:output_FL" "${NAME}:playback_FL"
+ pw-link "mpv:output_FR" "${NAME}:playback_FR"
+ pw-link "${MICROPHONE}:capture_FL" "${NAME}:playback_FL"
+ pw-link "${MICROPHONE}:capture_FR" "${NAME}:playback_FL"
+ pw-link "${NAME}:monitor_FL" "${NAME}-MIC:input_FL"
+ pw-link "${NAME}:monitor_FR" "${NAME}-MIC:input_FR"
+ else
+ echo -e "\nNode $PROGRAM not found."
+ fi
+}
+
+unload_sinks () {
+ read -r -p "Are you sure? [y/N] " response
+ response=${response,,} # tolower
+ if [[ "$response" =~ ^(yes|y)$ ]]; then
+ pactl list short modules | grep "$NAME" | cut -f1 | xargs -L1 pactl unload-module
+ echo -e "\nSinks unloaded."
+ else
+ echo -e "\nSink unloading aborted."
+ fi
+}
+
+
+reset_sinks () {
+ echo -e "Yes\nNo" \
+ | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "#900D09" -sf "$dm6" -p "Reset all sinks?" \
+ | grep "Yes" &> /dev/null && for INPUT in "${INPUTS[@]}"; do pactl move-sink-input "$(echo $INPUT | awk -F' ~ ' '{print $1}' | sed "s|#||")" "alsa_output.pci-0000_00_1b.0.analog-stereo"; done \
+ && $HOME/stuf/scripts/notification_wrapper.sh "\nALL SINKS HAVE BEEN RESET" "MINIVAC"
+}
+
+notify_sinks () {
+ treat_inputs
+ $HOME/stuf/scripts/notification_wrapper.sh "\nLIST OF CURRENT MAPPINGS\n$(for INPUT in "${INPUTS[@]}"; do echo "\n$INPUT"; done)" "MINIVAC"
+}
+
+change_sink () {
+ treat_inputs
+ CHOICE_INPUT=$( echo "$(for INPUT in "${INPUTS[@]}"; do echo $INPUT; done)" | uniq | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "$dm7" )
+ CHI_COMMAND=$(echo $CHOICE_INPUT | awk -F' ~ ' '{print $1}' | sed "s|#||")
+ CHI_NOTIFY=$(echo $CHOICE_INPUT | awk -F' ~ ' '{print $2}' )
+
+ [[ -n $CHI_COMMAND ]] && CHOICE_SINK=$( echo "$(for SINK in "${SINKS[@]}"; do echo $SINK; done)" | uniq | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "Select Sink:" )
+ CHS_COMMAND=$(echo $CHOICE_SINK | awk -F' ~ ' '{print $2}' )
+ CHS_NOTIFY=$(echo $CHOICE_SINK | awk -F' ~ ' '{print $3}' )
+
+ [[ -n $CHI_COMMAND && -n $CHS_COMMAND ]] && pactl move-sink-input "$CHI_COMMAND" "$CHS_COMMAND" && $HOME/stuf/scripts/notification_wrapper.sh "\nSOURCE\n$CHI_NOTIFY\nHAS BEEN MOVED TO SINK\n$CHS_NOTIFY" "MINIVAC"
+}
+
+######################### EXECUTION #########################
+
+# [[ -z $( grep -E "^alsa_output.pci-0000_03_04.0.analog-stereo$" $HOME/.config/pulse/* ) || -z $( grep -E "^alsa_input.pci-0000_03_04.0.iec958-stereo$" $HOME/.config/pulse/* ) ]] \
+# && $HOME/stuf/scripts/notification_wrapper.sh "\nSYSTEM CHANGE DETECTED, TERMINATING PROGRAM" "MINIVAC" && exit
+
+while getopts "hlunrc" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send sinks] [-l load sinks] [-u unload sinks] [-r reset sinks]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ l) load_sinks; exit ;; # LOAD ALL SINKS
+ u) unload_sinks; exit ;; # UNLOAD ALL SINKS
+ n) notify_sinks; exit ;; # LIST ALL SINKS VIA WRAPPER
+ r) reset_sinks; exit ;; # RESET ALL SINKS
+ c) connect_nodes; exit ;; # RESET ALL SINKS
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+change_sink
diff --git a/miscripts/DICTIONARY b/miscripts/DICTIONARY
new file mode 100644
index 0000000..d7765b5
--- /dev/null
+++ b/miscripts/DICTIONARY
@@ -0,0 +1,9 @@
+$ rsync ftp.edrdg.org::nihongo/JMdict_e ~/Downloads/JMdict_e
+
+NUKLEAR FLASHCARDS
+
+DEAR IMGUI IMMEDIATE MODE
+DYNAMICALLY GENERATES A CHAIN OF FLASHCARDS BASED ON CURSOR SELECTION (xclip -selection primary -o, -selection clipboard if you want it triggered by ctrl+C instead)
+
+EACH FLASHCARD HAS A LINK TO A LOOKUP WEBSITE OF THE USER'S CHOICE (%s = search string)
+ABILITY TO READ INPUT OVER A NETWORK IN CASE SOMEONE WANTS TO USE A DIFFERENT DEVICE FOR THE TRANSLATION WINDOW \ No newline at end of file
diff --git a/miscripts/Export Chrome Bookmarks as HTML b/miscripts/Export Chrome Bookmarks as HTML
new file mode 100644
index 0000000..7bc5c51
--- /dev/null
+++ b/miscripts/Export Chrome Bookmarks as HTML
@@ -0,0 +1,54 @@
+#credits: Mostly to tobibeer and Snak3d0c @ https://stackoverflow.com/questions/47345612/export-chrome-bookmarks-to-csv-file-using-powershell
+#Path to chrome bookmarks
+$pathToJsonFile = "$env:localappdata\Google\Chrome\User Data\Default\Bookmarks"
+
+$htmlOut = 'C:\temp\ChromeBookmarks.html'
+$htmlHeader = @'
+<!DOCTYPE NETSCAPE-Bookmark-file-1>
+<!--This is an automatically generated file.
+ It will be read and overwritten.
+ Do Not Edit! -->
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
+<Title>Bookmarks</Title>
+<H1>Bookmarks</H1>
+<DL><p>
+'@
+
+$htmlHeader | Out-File -FilePath $htmlOut -Force -Encoding utf8 #line59
+
+#A nested function to enumerate bookmark folders
+Function Get-BookmarkFolder {
+[cmdletbinding()]
+Param(
+[Parameter(Position=0,ValueFromPipeline=$True)]
+$Node
+)
+
+Process
+{
+
+ foreach ($child in $node.children)
+ {
+ $da = [math]::Round([double]$child.date_added / 1000000) #date_added - from microseconds (Google Chrome {dates}) to seconds 'standard' epoch.
+ $dm = [math]::Round([double]$child.date_modified / 1000000) #date_modified - from microseconds (Google Chrome {dates}) to seconds 'standard' epoch.
+ if ($child.type -eq 'Folder')
+ {
+ " <DT><H3 FOLDED ADD_DATE=`"$($da)`">$($child.name)</H3>" | Out-File -FilePath $htmlOut -Append -Force -Encoding utf8
+ " <DL><p>" | Out-File -FilePath $htmlOut -Append -Force -Encoding utf8
+ Get-BookmarkFolder $child
+ " </DL><p>" | Out-File -FilePath $htmlOut -Append -Force -Encoding utf8
+ }
+ else
+ {
+ " <DT><a href=`"$($child.url)`" ADD_DATE=`"$($da)`">$($child.name)</a>" | Out-File -FilePath $htmlOut -Append -Encoding utf8
+ } #else url
+ } #foreach
+ } #process
+} #end function
+
+$data = Get-content $pathToJsonFile -Encoding UTF8 | out-string | ConvertFrom-Json
+$sections = $data.roots.PSObject.Properties | select -ExpandProperty name
+ForEach ($entry in $sections) {
+ $data.roots.$entry | Get-BookmarkFolder
+}
+'</DL>' | Out-File -FilePath $htmlOut -Append -Force -Encoding utf8
diff --git a/miscripts/check_ssh.sh b/miscripts/check_ssh.sh
new file mode 100644
index 0000000..c423845
--- /dev/null
+++ b/miscripts/check_ssh.sh
@@ -0,0 +1,10 @@
+function check_ssh() {
+ ssh -o PasswordAuthentication=no "$@" true 2>&1 > /dev/null
+ [ $? == 0 ] && {
+ echo Connection successful
+ } || {
+ echo Connection failure
+ }
+}
+
+#check_ssh -i ~/.ssh/id_rsa tori_kago@torikago.com \ No newline at end of file
diff --git a/miscripts/choice.sh b/miscripts/choice.sh
new file mode 100644
index 0000000..c2f4e16
--- /dev/null
+++ b/miscripts/choice.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# Bash Menu Script Example
+
+PS3='Please enter your choice: '
+options=("Option 1" "Option 2" "Option 3" "Quit")
+select opt in "${options[@]}"
+do
+ case $opt in
+ "Option 1")
+ echo "you chose choice 1"
+ ;;
+ "Option 2")
+ echo "you chose choice 2"
+ ;;
+ "Option 3")
+ echo "you chose choice $REPLY which is $opt"
+ ;;
+ "Quit")
+ break
+ ;;
+ *) echo "invalid option $REPLY";;
+ esac
+done \ No newline at end of file
diff --git a/miscripts/delfi_rss.py b/miscripts/delfi_rss.py
new file mode 100644
index 0000000..80fe24d
--- /dev/null
+++ b/miscripts/delfi_rss.py
@@ -0,0 +1,3 @@
+https://www.delfi.rs/pretraga?q=&c=5&per_page=20&s=najnovije&cena_od=0&cena_do=0&izdavaci=%5B%5D&pismo=%5B%5D&povez=%5B%5D&uzrast=%5B%5D&autor=%5B%5D&datum_unosa=%5B%5D&platforma=%5B%5D&zanr=%5B10078%5D&podzanr=%5B%5D&boks=%5B%5D&kategorija=%5B%5D&stanje=1
+
+https://www.delfi.rs/pretraga?q=&c=5&per_page=50&s=najnovije&cena_od=0&cena_do=0&izdavaci=%5B%5D&pismo=%5B%5D&povez=%5B%5D&uzrast=%5B%5D&autor=%5B%5D&datum_unosa=%5B%5D&platforma=%5B%5D&zanr=%5B40127%5D&podzanr=%5B%5D&boks=%5B%5D&kategorija=%5B%5D&stanje=1 \ No newline at end of file
diff --git a/miscripts/hypothetical_cliparmyknife_mod.txt b/miscripts/hypothetical_cliparmyknife_mod.txt
new file mode 100644
index 0000000..e5f6b4d
--- /dev/null
+++ b/miscripts/hypothetical_cliparmyknife_mod.txt
@@ -0,0 +1,9 @@
+
+printf 'file.mp4\n%.0s' {1..118} > files.txt
+cat inputs.txt | tr -d "\n" | sed -r "s|file |\ -i |g" > input.txt
+
+
+for i in {0..119}; do echo "[$i:v:0][$i:a:0]"; done > filters.txt
+cat filters.txt | tr -d "\n" > filter.txt
+
+ffmpeg $(cat input.txt) -filter_complex "$(cat filter.txt)" -map "[outv]" -map "[outa]" out.mkv \ No newline at end of file
diff --git a/miscripts/hypothetical_picarmyknife.txt b/miscripts/hypothetical_picarmyknife.txt
new file mode 100644
index 0000000..bb33890
--- /dev/null
+++ b/miscripts/hypothetical_picarmyknife.txt
@@ -0,0 +1,11 @@
+# STACK MULTIPLE IMAGES SIDE BY SIDE / TOP TO BOTTOM
+# INTERACTIVE CROP, SHOW PREVIEW IN SEPARATE WINDOW
+# BREAK DOWN GIF INTO FRAMES AND VICE VERSA
+# REMOVE BLACK BORDERS
+# ADD WATERMARK
+# APPLY BASIC BITCH IMAGE FILTERS
+# LOOK UP WHAT ELSE IT CAN DO ON THE WEBSITE, AND MAYBE ASK SOMEONE FOR IDEAS
+# COLOR PICKER
+
+
+# Automatically adding transparency and removing whitespace from images.mp4
diff --git a/miscripts/imnotgivingup.sh b/miscripts/imnotgivingup.sh
new file mode 100644
index 0000000..0634e20
--- /dev/null
+++ b/miscripts/imnotgivingup.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+
+# USE THIS WHEN IT'S TIME TO COMPILE A LIST OF NAMES FOR YOUR *ACTUAL* ART SORTING PROGRAM, YOU BITCH
+
+ART_DIR="$HOME/art"
+
+IMAGE_EXT_FIND="\(png\|jpe?g\|gif\|bmp\|webp\)"
+IMAGE_EXT="$(echo "$IMAGE_EXT_FIND" | sed -r "s|\\\\\)|)|g; s|\\\\\(|(|g")"
+
+find "$ART_DIR" -type f -iregex ".*\.${IMAGE_EXT_FIND}$" | sed -r "s|$ART_DIR\/||g; s|\/[0-9]+\_p[0-9]+\.${IMAGE_EXT}$|\/PIXIV_TAG|g; s|\/danbooru(\_[a-z0-9]+){2}\.${IMAGE_EXT}$|\/DANBOORU_TAG|g; s|[0-9]*[^0-9]*?\.${IMAGE_EXT}$||g" | sort | uniq | sed '/^$/d'
+
+
+
+
diff --git a/miscripts/inotifytest.sh b/miscripts/inotifytest.sh
new file mode 100644
index 0000000..860c7ad
--- /dev/null
+++ b/miscripts/inotifytest.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# requires notify-tools
+
+inotifywait -m ~/Tempsktop -e create -e moved_to |
+ while read dir action file; do
+ echo "The file '$file' appeared in directory '$dir' via '$action'"
+ [[ -n $(echo "$file" | grep "png") ]] && feh "${dir}${file}"
+ done
diff --git a/miscripts/minivac_pulse.sh b/miscripts/minivac_pulse.sh
new file mode 100644
index 0000000..c6f2cbc
--- /dev/null
+++ b/miscripts/minivac_pulse.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+
+
+#MPV WINDOW / SOME OTHER OUTPUT
+#pactl list sink-inputs | grep -E "^\s*Sink\ Input\ \#|^\s*media\.name\ \=\ " | tac | grep -A 1 -E "^\s*media\.name\ \=\ .*Onii.*" | grep -oP "Sink\ Input\ \#\K[0-9]+"
+
+#SINK
+#pacmd list-sinks | grep -E "^\s*name:|^\s*module:" | grep -A 1 -E "^\s*name: <CombinedDiscordSink>" | grep -oP "module: \K[0-9]+"
+
+######################### VARIABLES #########################
+
+MICROPHONE="alsa_input.pci-0000_00_1b.0.analog-stereo"
+SPEAKERS="alsa_output.pci-0000_00_1b.0.analog-stereo"
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '------------------------------------------------------------------------------------------'
+ 'Change Sink | minivac.sh | Ctrl+Shift+F7'
+ 'Load Sinks | minivac.sh -l | -'
+ 'Unload Sinks | minivac.sh -u | -'
+ 'Notify-send Sinks | minivac.sh -n | F7'
+ 'Reset Sinks | minivac.sh -r | Shift+F7'
+)
+
+DMENU_APPEARANCE="mononoki;11;#191919;#AAAAAA;#890089;#FFFFFF;Select Input:"
+
+for i in {1..7}
+do
+ declare "dm$i=$(echo $DMENU_APPEARANCE | cut -f$i -d';')" # CONVERT DMENU APPEARANCE STRIP INTO SEPARATE PARAMETERS
+done
+
+mapfile -t INPUTS < <( pactl list sink-inputs | grep -B 30 -A 10 "application.process.binary" | grep "Sink Input\|application\.name\|media\.name" | grep -B 2 "application\.name" | sed -r "/--/d; s|Sink\ Input\ ||g; s|[[:space:]]*application\.name\ \=|\~|g; s|[[:space:]]*media\.name\ \=\ |\~\ |g" | paste -sd ' \n' )
+
+mapfile -t SINKS < <( pactl list sinks | grep -A 3 "Sink #" | grep "Sink #\|Name:\|Description:" | sed -r "s|[[:space:]]*Name:\ |\~ |g; s|[[:space:]]*Description:\ |\~\ |g" | paste -sd ' \n' )
+
+######################### HELPER FUNCTIONS #########################
+
+treat_inputs () {
+ i=0
+ for INPUT in "${INPUTS[@]}"
+ do
+ SINK=$( pactl list sink-inputs | grep -A 5 "$( echo $INPUT | sed -r "s| .*||g" )" | grep "Sink:")
+ SINK_TRANSLATED=$( pactl list sinks | grep -A 3 "$( echo $SINK | sed -r 's|: | #|g')" | grep "Description" | sed -r "s|.*Description: ||g" )
+ INPUTS[i]=$( echo "$INPUT -> $SINK_TRANSLATED" )
+ ((i++))
+ done
+}
+
+load_sinks () {
+ if [[ -z "$(pactl list short modules | grep "DiscordSink")" ]]; then
+ pactl load-module module-null-sink sink_name=VirtualDiscordSink
+ pactl load-module module-loopback source="$MICROPHONE" sink=VirtualDiscordSink
+ pactl load-module module-combine-sink slaves=VirtualDiscordSink,$SPEAKERS sink_name=CombinedDiscordSink
+ else
+ echo -e "\nSinks already loaded."
+ fi
+}
+
+unload_sinks () {
+ read -r -p "Are you sure? [y/N] " response
+ response=${response,,} # tolower
+ if [[ "$response" =~ ^(yes|y)$ ]]; then
+ pactl list short modules | grep "DiscordSink" | cut -f1 | xargs -L1 pactl unload-module
+ echo -e "\nSinks unloaded."
+ else
+ echo -e "\nSink unloading aborted."
+ fi
+}
+
+configure_mic () {
+ MIC_NAME=$( pactl list source-outputs | grep -B 18 "WEBRTC VoiceEngine" | grep "Source Output #" | sed -r "s|^.*#||g" )
+ MIC_SOURCE=$( pactl list source-outputs | grep -B 18 "WEBRTC VoiceEngine" | grep "Source:" | sed -r "s|^.*:\ ||g" )
+ SINK_SOURCE=$( pactl list sources | grep -B 3 "VirtualDiscordSink.monitor" | grep "Source #" | sed -r "s|^.*#||g" )
+ [[ "$MIC_SOURCE" -ne "$SINK_SOURCE" ]] && pactl move-source-output "$MIC_NAME" "VirtualDiscordSink.monitor" && $HOME/stuf/scripts/notification_wrapper.sh "\nMICROPHONE\nHAS BEEN MOVED TO SINK\n$SINK_SOURCE" "MINIVAC"
+}
+
+reset_sinks () {
+ echo -e "Yes\nNo" \
+ | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "#900D09" -sf "$dm6" -p "Reset all sinks?" \
+ | grep "Yes" &> /dev/null && for INPUT in "${INPUTS[@]}"; do pactl move-sink-input "$(echo $INPUT | awk -F' ~ ' '{print $1}' | sed "s|#||")" "alsa_output.pci-0000_00_1b.0.analog-stereo"; done \
+ && $HOME/stuf/scripts/notification_wrapper.sh "\nALL SINKS HAVE BEEN RESET" "MINIVAC"
+}
+
+notify_sinks () {
+ treat_inputs
+ $HOME/stuf/scripts/notification_wrapper.sh "\nLIST OF CURRENT MAPPINGS\n$(for INPUT in "${INPUTS[@]}"; do echo "\n$INPUT"; done)" "MINIVAC"
+}
+
+change_sink () {
+ treat_inputs
+ CHOICE_INPUT=$( echo "$(for INPUT in "${INPUTS[@]}"; do echo $INPUT; done)" | uniq | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "$dm7" )
+ CHI_COMMAND=$(echo $CHOICE_INPUT | awk -F' ~ ' '{print $1}' | sed "s|#||")
+ CHI_NOTIFY=$(echo $CHOICE_INPUT | awk -F' ~ ' '{print $2}' )
+
+ [[ -n $CHI_COMMAND ]] && CHOICE_SINK=$( echo "$(for SINK in "${SINKS[@]}"; do echo $SINK; done)" | uniq | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "Select Sink:" )
+ CHS_COMMAND=$(echo $CHOICE_SINK | awk -F' ~ ' '{print $2}' )
+ CHS_NOTIFY=$(echo $CHOICE_SINK | awk -F' ~ ' '{print $3}' )
+
+ [[ -n $CHI_COMMAND && -n $CHS_COMMAND ]] && pactl move-sink-input "$CHI_COMMAND" "$CHS_COMMAND" && $HOME/stuf/scripts/notification_wrapper.sh "\nSOURCE\n$CHI_NOTIFY\nHAS BEEN MOVED TO SINK\n$CHS_NOTIFY" "MINIVAC"
+}
+
+######################### EXECUTION #########################
+
+# [[ -z $( grep -E "^alsa_output.pci-0000_03_04.0.analog-stereo$" $HOME/.config/pulse/* ) || -z $( grep -E "^alsa_input.pci-0000_03_04.0.iec958-stereo$" $HOME/.config/pulse/* ) ]] \
+# && $HOME/stuf/scripts/notification_wrapper.sh "\nSYSTEM CHANGE DETECTED, TERMINATING PROGRAM" "MINIVAC" && exit
+
+load_sinks
+configure_mic
+
+while getopts "hlunr" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send sinks] [-l load sinks] [-u unload sinks] [-r reset sinks]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ l) load_sinks; exit ;; # LOAD ALL SINKS
+ u) unload_sinks; exit ;; # UNLOAD ALL SINKS
+ n) notify_sinks; exit ;; # LIST ALL SINKS VIA WRAPPER
+ r) reset_sinks; exit ;; # RESET ALL SINKS
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+change_sink
+
+# pacmd load-module module-loopback source=MICROPHONE_SOURCE sink=Virtual
+# alsa_input.usb-C-Media_Electronics_Inc._YMC_1040-00.mono-fallback
+# pacmd load-module module-combine-sink slaves=Virtual,SOUNDCARD_SINK
+# alsa_output.pci-0000_03_04.0.analog-stereo
+# set mpv playback to simultaneous output to null output, CMI...
+# set recording to monitor of null output
+
+
+
+
+# pactl list sinks -> Sink #1
+# pactl list sink-inputs -> Sink: 1
diff --git a/miscripts/postman.sh b/miscripts/postman.sh
new file mode 100644
index 0000000..3b62240
--- /dev/null
+++ b/miscripts/postman.sh
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+URL="http://172.26.16.1:8080/klk"
+
+FILE="$HOME/csv.csv"
+
+function loadPredmeti () {
+ echo -e "\n\n\n---------- LOADING PREDMETI ----------"
+ while read line; do
+ curl --location "${URL}/predmeti" \
+ --header 'Content-Type: application/json' \
+ --data ' {
+ "naziv": "'"$line"'"
+ }'
+ done < <( tail -n +2 $FILE | iconv -c -f utf-8 -t ascii | sed -r "s|\",\"|~|g; s|~ET~|~CET~|g; s|\"||g" | cut -d"~" -f 1 | sort | uniq | tr -d '\r' )
+}
+
+function loadProfesori () {
+ echo -e "\n\n\n---------- LOADING PROFESORI ----------"
+ while read line; do
+ ime="${line##* }"
+ prezime="${line%% *}"
+
+ curl --location "${URL}/profesori" \
+ --header 'Content-Type: application/json' \
+ --data ' {
+ "ime": "'"$ime"'",
+ "prezime": "'"$prezime"'"
+ }'
+ done < <( tail -n +2 $FILE | iconv -c -f utf-8 -t ascii | sed -r "s|\",\"|~|g; s|~ET~|~CET~|g; s|\"||g" | cut -d"~" -f 3 | sort | uniq | tr -d '\r' )
+}
+
+function loadGrupe () {
+ echo -e "\n\n\n---------- LOADING GRUPE ----------"
+ while read line; do
+ curl --location "${URL}/grupe" \
+ --header 'Content-Type: application/json' \
+ --data ' {
+ "oznaka": "'"$line"'"
+ }'
+ done < <( tail -n +2 $FILE | iconv -c -f utf-8 -t ascii | sed -r "s|\",\"|~|g; s|~ET~|~CET~|g; s|\"||g" | cut -d"~" -f 4 | sort | uniq | tr ',' '\n' | sed -s "s| ||g" | sort | uniq | tr -d '\r' )
+}
+
+
+function loadUcionice () {
+ echo -e "\n\n\n---------- LOADING UCIONICE ----------"
+ while read line; do
+ curl --location "${URL}/ucionice" \
+ --header 'Content-Type: application/json' \
+ --data ' {
+ "oznaka": "'"$line"'"
+ }'
+ done < <( tail -n +2 $FILE | iconv -c -f utf-8 -t ascii | sed -r "s|\",\"|~|g; s|~ET~|~CET~|g; s|\"||g" | cut -d"~" -f 7 | sort | uniq | tr -d '\r' )
+}
+
+LINENUM=0
+
+function loadTermini () {
+ echo -e "\n\n\n---------- LOADING TERMINI ----------"
+ while read line; do
+
+ pocetni_termin="$( echo "$line" | cut -d"~" -f 6 | cut -d"-" -f 1 | tr -d '\r' )"
+ krajnji_termin="$( echo "$line" | cut -d"~" -f 6 | cut -d"-" -f 2 | tr -d '\r' )"
+ dan_u_nedelji="$( echo "$line" | cut -d"~" -f 5 | tr -d '\r' )"
+
+ tip_nastave="$( echo "$line" | cut -d"~" -f 2 | tr -d '\r' )"
+
+ ucionica_oznaka="$( echo "$line" | cut -d"~" -f 7 | tr -d '\r' )"
+
+ profesor_ime="$( echo "$line" | cut -d"~" -f 3 | cut -d" " -f 2- | tr -d '\r' )"
+ profesor_prezime="$( echo "$line" | cut -d"~" -f 3 | cut -d" " -f 1 | tr -d '\r' )"
+
+ predmet_naziv="$( echo "$line" | cut -d"~" -f 1 | tr -d '\r' )"
+
+ jank=""
+
+ IFS=", " read -ra GRPS <<< $( echo -e "$line" | cut -d"~" -f 4 | tr -d '\r' )
+ for grp in "${GRPS[@]}"; do
+ jank="${jank}{ \"oznaka\": \"${grp}\" }, "
+ done
+
+ jank="${jank::-2}"
+
+ curl --location "${URL}/termini" \
+ --header 'Content-Type: application/json' \
+ --data ' {
+ "pocetniTermin": "'"$pocetni_termin"'",
+ "krajnjiTermin": "'"$krajnji_termin"'",
+ "danUNedelji": "'"$dan_u_nedelji"'",
+ "tipNastave": "'"$tip_nastave"'",
+ "ucionica": {
+ "oznaka": "'"$ucionica_oznaka"'"
+ },
+ "profesor": {
+ "ime": "'"$profesor_ime"'",
+ "prezime": "'"$profesor_prezime"'"
+ },
+ "predmet": {
+ "naziv": "'"$predmet_naziv"'"
+ },
+ "grupe": [ '"$jank"' ]
+ }'
+
+ done < <( tail -n +2 $FILE | iconv -c -f utf-8 -t ascii | sed -r "s|\",\"|~|g; s|~ET~|~CET~|g; s|\"||g" | sort | uniq )
+}
+
+
+
+loadPredmeti
+loadProfesori
+loadGrupe
+loadUcionice
+
+sleep 1
+loadTermini
+
+echo -e "\n\n\nDone"
diff --git a/miscripts/qemu_helper.sh b/miscripts/qemu_helper.sh
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/miscripts/qemu_helper.sh
diff --git a/miscripts/rshred.txt b/miscripts/rshred.txt
new file mode 100644
index 0000000..99d6708
--- /dev/null
+++ b/miscripts/rshred.txt
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+NOTIFY="console"
+DELETE=""
+SHRED_OPTIONS=""
+FILES=()
+
+while [ -n "${1}" ]; do
+ case "${1}" in
+
+ "--zenity" )
+ NOTIFY="zenity"
+ ;;
+
+ "-q" | "--quiet" )
+ NOTIFY=""
+ ;;
+
+ "-u" | "--remove" )
+ SHRED_OPTIONS="${SHRED_OPTIONS} ${1}"
+ DELETE="true"
+ ;;
+
+ "-n" | "-s" )
+ SHRED_OPTIONS="${SHRED_OPTIONS} ${1} ${2}"
+ shift
+ ;;
+
+ "--")
+ shift
+ while [[ -n "${1}" ]]; do
+ [[ -e "${1}" ]] && FILES=( "${FILES[@]}" "${1}" )
+ shift
+ done
+ break
+ ;;
+
+ -* )
+ SHRED_OPTIONS="${SHRED_OPTIONS} ${1}"
+ ;;
+
+ * )
+ #FILES[${#FILES[*]}]="${1}"
+ if [ -e "${1}" ]; then
+ FILES=( "${FILES[@]}" "${1}" )
+ fi
+ ;;
+
+ esac
+
+ shift
+
+done
+
+for F in "${FILES[@]}"; do
+ [[ -e "${F}" ]] || continue
+ if [ -d "${F}" ]; then
+ find "${F}" -type f -exec shred ${SHRED_OPTIONS} {} +
+ [[ "${DELETE}" = "true" ]] && rm -r "${F}"
+ else
+ shred ${SHRED_OPTIONS} "${F}"
+ fi
+done
+
+exit 0
diff --git a/miscripts/shigoto_kfxx.sh b/miscripts/shigoto_kfxx.sh
new file mode 100644
index 0000000..29526ce
--- /dev/null
+++ b/miscripts/shigoto_kfxx.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+
+FIVEXX_ONLY=true
+CONTAINER_SELECT=false
+WRITE_TO_FILE=false
+FLAGS=""
+HELP_TEXT="script usage: $(basename $0) [-h (prints this message)] [-a (print all logs, not just 5xx errors)] [-c (view logs of a certain container)] [-C (view logs of all containers)] [-p (previous pod state)] [-s TIMEDELTA (print logs since TIMEDELTA)] pod_name"
+
+while getopts 'hacCwps:' OPTION; do
+ case "$OPTION" in
+ h)
+ echo "$HELP_TEXT" >&2 ; exit 0
+ ;;
+ a)
+ FIVEXX_ONLY=false
+ ;;
+ c)
+ CONTAINER_SELECT=true
+ ;;
+ C)
+ [[ $CONTAINER_SELECT == true ]] && echo "ERROR: -C AND -c CAN'T BE USED AS PART OF THE SAME COMMAND" && exit -2
+ FLAGS="$FLAGS --all-containers"
+ echo "VIEWING LOGS OF ALL CONTAINERS IN POD"
+ ;;
+ p)
+ FLAGS="$FLAGS -p"
+ echo "VIEWING LOGS OF POD'S PREVIOUS STATE"
+ ;;
+ w)
+ WRITE_TO_FILE=true
+ echo "WRITE TO FILE: TRUE"
+ ;;
+ s)
+ TIMEDELTA="$OPTARG"
+ echo "TIME DELTA SPECIFIED: $TIMEDELTA"
+ FLAGS="$FLAGS --since=$TIMEDELTA"
+ ;;
+ ?)
+ echo "$HELP_TEXT" >&2 ; exit 0
+ exit 0
+ ;;
+ esac
+done
+shift "$(($OPTIND -1))"
+
+POD_ARG="$1"
+ENV="$( kubectx -c | cut -d "-" -f3 )"
+POD_LIST="$( kubectl get pods --all-namespaces | grep "$POD_ARG" | awk '$0!=""{print NR, $0}' )"
+POD_NUM="$( echo "$POD_LIST" | wc -l )"
+POD=""
+CONTAINER=""
+
+get_containers () {
+ CONTAINER_LIST="$( kubectl get pods "$POD" -o jsonpath="{.spec['containers','initContainers'][*].name}" | tr ' ' '\n' | awk '$0!=""{print NR, $0}' )"
+ CONTAINER_NUM="$( echo "$CONTAINER_LIST" | wc -l )"
+ if [[ "$CONTAINER_NUM" -gt 1 ]]; then
+ echo -e "\nLIST OF CONTAINERS IN POD ${POD}:\n${CONTAINER_LIST}\n"
+ read -p 'PLEASE SELECT POD NUMBER: ' containerindex
+ [[ "$containerindex" =~ [0-9]+ ]] || echo "FUNKY INPUT, DEFAULTING TO 1" && containerindex=1
+ if [[ "$containerindex" -le "$CONTAINER_NUM" ]]; then
+ CONTAINER="$( echo "$CONTAINER_LIST" | sed -n "${containerindex}p" | sed -r "s|^[^ ]* *||g" )"
+ else
+ echo "ERROR: INDEX OF CONTAINER EXCEEDS NUMBER OF CONTAINERS FOUND INSIDE POD"
+ exit 1
+ fi
+ elif [[ "$CONTAINER_NUM" -eq 1 ]]; then
+ CONTAINER="$( echo "$CONTAINER_LIST" | sed -r "s|^[^ ]* *||g" )"
+ else
+ echo "NO CONTAINERS FOUND IN $POD"
+ exit -1
+ fi
+ FLAGS="$FLAGS --container="$CONTAINER""
+}
+
+get_logs () {
+ grepstr=""
+ echostr="ALL LOGS FOR POD ${POD}:\n"
+ tmp_file="$( mktemp -p "/tmp" "${POD}_logs_tmp_XXXXX" )"
+ file="${POD}_logs.txt"
+ NS="$( echo "$POD_LIST" | grep "$POD" | sed -r "s|^[^ ]* *([^ ]*) *(.*?)|\1|g" )"
+ [[ ! $( kubens -c | grep "$NS" ) ]] && kubens "$NS"
+ [[ $( kubens -c | grep "$NS" ) ]] || exit -1
+ [[ $CONTAINER_SELECT == true ]] && get_containers && echostr="$( echo "$echostr" | sed -r "s|:| (container $CONTAINER):|g" )"
+ [[ ! $FIVEXX_ONLY ]] && echostr="$( echo "$echostr" | sed -r "s|^ALL|5XX|" )" && grepstr="[^\.0-9a-zA-Z\-]5[0-9]{2}[^\.0-9a-zA-Z\-]"
+ echo -e "$echostr"
+ kubectl logs "$POD" $FLAGS --timestamps=true --prefix=true | grep --color=always -E "$grepstr" | tee "$tmp_file"
+
+ [[ "$WRITE_TO_FILE" = "true" ]] && cat "$tmp_file" | sed -e 's/\x1b\[[0-9;]*m//g' > "$file" && echo "Logs saved to '$file'."
+
+ rm -f "$tmp_file"
+}
+
+[[ -z "$POD_ARG" ]] && echo "ERROR: MUST SPECIFY POD" && exit -1
+
+echo -e "CURRENT ENV: $ENV\n"
+
+if [[ "$POD_NUM" -gt 1 ]]; then
+ echo -e "FOUND MORE THAN ONE POD THAT MATCHES STRING:\n${POD_LIST}\n"
+ read -p 'PLEASE SELECT POD NUMBER: ' podindex
+ [[ "$podindex" =~ [0-9]+ ]] || echo "FUNKY INPUT, DEFAULTING TO 1" && podindex=1
+ if [[ "$podindex" -le "$POD_NUM" ]]; then
+ POD="$( echo "$POD_LIST" | sed -n "${podindex}p" | sed -r "s|^([^ ]* *){2}||g" | grep -o -E "^[^ ]*" )"
+ get_logs
+ else
+ echo "ERROR: INDEX OF POD EXCEEDS NUMBER OF PODS FOUND"
+ exit 1
+ fi
+elif [[ "$POD_NUM" -eq 1 ]]; then
+ POD="$( echo "$POD_LIST" | sed -r "s|^([^ ]* *){2}||g" | grep -o -E "^[^ ]*" )"
+ get_logs
+else
+ echo "POD NOT FOUND ON $ENV"
+ exit -1
+fi
diff --git a/miscripts/shigoto_trivy_sorting_hat.sh b/miscripts/shigoto_trivy_sorting_hat.sh
new file mode 100644
index 0000000..2f7e1ae
--- /dev/null
+++ b/miscripts/shigoto_trivy_sorting_hat.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+# version: 4.0
+
+VULN_REPORT_PREV=""
+VULN_REPORT_FULL="vulnerable_images_full_$( date +'%Y-%m-%d_%s' ).txt"
+VULN_REPORT_NEW="vulnerable_images_to_report.txt"
+
+[ "$#" -lt 1 ] && echo "script usage: $(basename $0) [-h (prints this message)] [-p previous_report.txt] prometheus_dump.txt" >&2 && exit 0
+
+while getopts 'hp:' OPTION; do
+ case "$OPTION" in
+ h)
+ echo "script usage: $(basename $0) [-h (prints this message)] [-p previous_report.txt] prometheus_dump.txt" >&2 ; exit 0
+ ;;
+ p)
+ VULN_REPORT_PREV="$OPTARG"
+ echo "Previous trivy report: $VULN_REPORT_PREV"
+[[ -z "$VULN_REPORT_PREV" || ! $(file "$VULN_REPORT_PREV" | grep "txt: ASCII text") ]] && echo "ERROR: Option -p requires .txt file of previous report" && exit -1
+ ;;
+ ?)
+ echo "script usage: $(basename $0) [-h (prints this message)] [-p previous_report.txt] prometheus_dump.txt" >&2
+ exit 0
+ ;;
+ esac
+done
+shift "$(($OPTIND -1))"
+
+TRIVY_DUMP="$1"
+[[ -z "$TRIVY_DUMP" || ! $(file "$TRIVY_DUMP" | grep "txt: ASCII text") ]] && echo "ERROR: Script requires .txt file of raw alert data from Prometheus" && exit -1
+
+ENV="$( grep -o -E "cluster=([A-Z0-9]+-?)+" "$TRIVY_DUMP" | head -n 1 | cut -d "-" -f 3 )"
+
+[[ -z $( echo "$ENV" | grep -E "PROD|QSS|OAE|FAE" ) ]] && echo "ERROR: ENVIRONMENT NOT FOUND" && exit -1
+
+VULN_REPORT_FULL="${ENV}_${VULN_REPORT_FULL}"
+VULN_REPORT_NEW="${ENV}_${VULN_REPORT_NEW}"
+
+{ echo "REPORT TIMESTAMP: $(date +'%Y-%m-%d %H:%M')" ; cat "$TRIVY_DUMP" | sed -r "s/active=truealertname=ATTENTION>>>ImageVulnerabilitiesFound<<<WARNINGcluster=WE-POS-(DEV|FAE|OAE|QSS|PROD)-AKS-(SUPPORT|SVC)(-1|-2)?cluster_short_name=.*?cluster_type=.*?image_repository=/Image /g; s/image_tag=/:/g; s/namespace=/ in namespace /g; s/pos_alert=.*$//g" | sed '/Image/! d' | sort -V ; } > "$VULN_REPORT_FULL"
+
+VULN_REPORT_TMP="$( mktemp -p "/tmp" "vuln_report_tmp_XXXXX" )"
+if [[ -n "$VULN_REPORT_PREV" ]]; then
+ tail -n +2 "$VULN_REPORT_FULL" | grep -vf "$VULN_REPORT_PREV" > "$VULN_REPORT_TMP"
+else
+ tail -n +2 "$VULN_REPORT_FULL" > "$VULN_REPORT_TMP"
+fi
+
+NAMESPACE="<NONE>"
+
+head -n 1 "$VULN_REPORT_FULL" | sed -r "s/REPORT\ TIMESTAMP:/CURRENT\ REPORT\ TIMESTAMP: /g" > "$VULN_REPORT_NEW"
+head -n 1 "$VULN_REPORT_PREV" | sed -r "s/REPORT\ TIMESTAMP:/PREVIOUS\ REPORT\ TIMESTAMP:/g" >> "$VULN_REPORT_NEW"
+date1=$( sed -n '1p' "$VULN_REPORT_NEW" | sed -r "s/^.*?:\ +[0-9]{2}([0-9]{2})\-([0-9]+)\-([0-9]+)\ .*?$/\1\2\3/g")
+date2=$( sed -n '2p' "$VULN_REPORT_NEW" | sed -r "s/^.*?:\ +[0-9]{2}([0-9]{2})\-([0-9]+)\-([0-9]+)\ .*?$/\1\2\3/g")
+echo -e "DAYS SINCE LAST REPORT: $(( ($(date --date="$date1" +%s) - $(date --date="$date2" +%s) )/(60*60*24) ))" >> "$VULN_REPORT_NEW"
+
+while IFS= read -r line
+do
+ echo "$line"
+ CURR_NAMESPACE="$( echo "$line" | sed -r "s/^.* in namespace //g")"
+ CURR_IMAGE="$( echo "$line" | sed -r "s/^Image //g ; s/ in namespace.*$//g")"
+ [[ "$CURR_NAMESPACE" != "$NAMESPACE" ]] && echo -e "\n\nNAMESPACE: ${CURR_NAMESPACE}\n" >> "$VULN_REPORT_NEW"
+ echo "$CURR_IMAGE" >> "$VULN_REPORT_NEW"
+ NAMESPACE="$CURR_NAMESPACE"
+done < "$VULN_REPORT_TMP"
+
+rm -f "$VULN_REPORT_TMP"
diff --git a/miscripts/snap.sh b/miscripts/snap.sh
new file mode 100644
index 0000000..0a5b887
--- /dev/null
+++ b/miscripts/snap.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# Basic snapshot-style rsync backup script
+
+# Config
+OPT="-aPh"
+LINK="--link-dest=$HOME/Downloads/Snapshots/username/last"
+SRC="/home/jay/TXTFILES/"
+SNAP="$HOME/Downloads/Snapshots/"
+LAST="$HOME/Downloads/Snapshots/last"
+date=`date "+%Y-%b-%d:_%T"`
+
+# Run rsync to create snapshot
+rsync $OPT $LINK $SRC ${SNAP}$date
+
+# Remove symlink to previous snapshot
+rm -f $LAST
+
+# Create new symlink to latest snapshot for the next backup to hardlink
+ln -s ${SNAP}$date $LAST
diff --git a/miscripts/wide_play_cmus.sh b/miscripts/wide_play_cmus.sh
new file mode 100644
index 0000000..eeb6e55
--- /dev/null
+++ b/miscripts/wide_play_cmus.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+
+# THE FINAL SOLUTION
+# DEPENDENCIES: sox, cmus
+# ANOTHER OPTION WOULD BE TO DMENU ALL THE OPTIONS, SO IT CAN BE RUN WITH ONLY ONE KEYBOARD SHORTCUT
+# AND I CAN'T HELP BUT THINK THAT THERE'S SOME WAY TO UNIFY THE PLAYLIST TYPES AND THE SINGLE-SONG BRANCH AND USE ONLY FLAGS INSTEAD OF IF
+# ALSO IT WOULD PROBABLY RUN FASTER IF I REPLACED EVERY FIND WITH LOCATE
+# OH WELL
+
+# look into types of parentheses, $(( )) is math for example
+# shuf is a thing
+# also xargs -r is a thing
+
+# [ "$(printf "Yes\\nNo" | dmenu -l 2 -i -p "Send $file to the trash?")" = "Yes" ]
+
+
+[[ ! $(pgrep -x cmus) ]] && xfce4-terminal -e cmus && exit
+
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '------------------------------------------------------------------------------------------'
+ 'Play Song | play.sh | F8'
+ 'Queue Song | play.sh -q | F9'
+ 'Notify-send Queue | play.sh -n | F10'
+ 'Play/Pause Song | cmus-remote -u | Shift+F8'
+ 'Skip to Next Song | cmus-remote --next | Shift+F9'
+ 'Toggle Autoplay | cmus-remote -C "toggle continue" | Ctrl+F9'
+ 'Play Playlist | play.sh -l | Ctrl+Shift+F8'
+ 'Queue Playlist | play.sh -lq | Ctrl+Shift+F9'
+ 'Clear Playlist | play.sh -c | Ctrl+Shift+F10'
+ 'Increase Volume | cmus-remote -v +5% | Shift+Upper side mouse button'
+ 'Decrease Volume | cmus-remote -v -5% | Shift+Lower side mouse button'
+)
+
+playlist_flag=false
+queue_flag=false
+clear_flag=false
+current_song=$(cmus-remote -Q | grep file | sed "s|file ||g")
+
+MUSIC="$HOME/music"
+SONG=""
+SED_STRING="s|^|player-play |"
+ROFI_THEME="BernBlue"
+
+#E0E04B
+#AAAA00
+
+while getopts "hnlqc" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send queue] [-l list] [-q queue] [-c clear]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+# n) notify-send -u low -t 15000 -i $HOME/stuf/rikanom.png "Songs currently in queue:" "$(cmus-remote -C 'save -q -' | sed -r 's|^\/([^\/]+\/)+||g' )"; exit ;; # NOTIFY-SEND QUEUE
+ n) "$HOME/stuf/scripts/notification_wrapper.sh" "\n> $( echo $current_song | sed -r 's|^\/([^\/]+\/)+||g' ) \n$( cmus-remote -C 'save -q -' | sed -r 's|^\/([^\/]+\/)+||g' | head -n 50 )" "UNIVPLAY" ; exit ;; # NOTIFY-SEND QUEUE VIA OWN WRAPPER
+ l) playlist_flag=true; ROFI_THEME="BernViolet" ;; # SET PLAYLIST FLAG, CHANGE DMENU COLOR
+ q) queue_flag=true; SED_STRING="s|^|add -q |"; ROFI_THEME="${ROFI_THEME}Queue" ;; # SET QUEUE FLAG, CHANGE DMENU TEXT
+ c) clear_flag=true; ROFI_THEME="BernRed" ;; # SET CLEAR FLAG, CHANGE DMENU COLOR/TEXT
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+if $clear_flag ; then
+
+ CHOICE=$(echo -e "1. Keep only the currently playing song\n2. Clear everything\n3. Abort" \
+ | rofi -dmenu -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 3 -no-click-to-exit )
+ echo "$CHOICE" | grep "1" &> /dev/null && cmus-remote -q -c
+ # fuck you cmus
+ echo "$CHOICE" | grep "2" &> /dev/null && cmus-remote -q -c && sox -n -r 44100 -c 2 /tmp/silence.wav trim 0.0 0.5 && cmus-remote -C "player-play /tmp/silence.wav" && rm /tmp/silence.wav && cmus-remote -C "set continue=false"
+ exit
+fi
+
+
+
+
+if $playlist_flag ; then
+
+ # FIND EVERY DIRECTORY WITH AN AUDIO FILE IN IT AND PIPE IT INTO DMENU
+ PL_DIR=$(find $MUSIC -type f -iregex ".*\.\(mp3\|flac\|m4a\|ogg\)$" -printf '%h/\n' | uniq \
+ | rofi -dmenu -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 15 -no-click-to-exit )
+
+ if [[ -n $PL_DIR ]]; then
+
+ $queue_flag || cmus-remote -q -c # || OPERATOR; IF QUEUE FLAG IS FALSE, THEN CLEAR THE QUEUE
+ find "$PL_DIR" -maxdepth 1 -type f | sort | sed "s|^|add -q |" | sed -r "s|'|\\\'|g" | xargs -I{} cmus-remote -C "{}" # FIND EVERY SONG IN THE DIRECTORY (IGNORING ANY NESTED DIRECTORIES) AND PIPE THEM INTO CMUS-REMOTE
+ $queue_flag || cmus-remote -q --next # || OPERATOR; IF QUEUE FLAG IS FALSE, PUSH THE FIRST-QUEUED SONG
+ $queue_flag || cmus-remote -p # || OPERATOR; IF QUEUE FLAG IS FALSE, PLAY THE PUSHED SONG
+ cmus-remote -C "set continue=true" # ENABLE AUTOPLAY
+ cmus-remote -C "set play_library=false" # DISABLE ORDINARY PLAYLIST PLAYBACK JUST IN CASE
+ fi
+
+else
+
+ # FIND EVERY AUDIO FILE AND PIPE IT INTO ROFI, IN TWO LINES
+ SONG=$( find $MUSIC -type f -iregex ".*\.\(mp3\|flac\|m4a\|ogg\)$" -printf '/%P\n' | sort --version-sort | sed -r "s|(^\/([^\/]+\/)+)(([^\/])+$)|\1\n\3\x0f|g; $ s|.{1}$||" | paste -sd '\n\0' \
+ | rofi -dmenu -sep '\x0f' -eh 2 -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 15 -no-click-to-exit | paste -sd '' )
+
+ [[ -n $SONG ]] && [[ -n "$current_song" ]] && ! $queue_flag && cmus-remote -C "add -Q $current_song" # IF QUEUE FLAG IS FALSE, PLACE CURRENTLY PLAYING SONG AT THE END OF THE QUEUE
+ [[ -n $SONG ]] && echo "${MUSIC}${SONG}" | sed "$SED_STRING" | cmus-remote # MUCH CLEANER THAN DMENU
+ [[ -n $SONG ]] && cmus-remote -C "set continue=$queue_flag" # TOGGLE AUTOPLAY BASED ON CIRCUMSTANCES
+
+fi
diff --git a/miscripts/wide_play_dmenu.sh b/miscripts/wide_play_dmenu.sh
new file mode 100644
index 0000000..96a9705
--- /dev/null
+++ b/miscripts/wide_play_dmenu.sh
@@ -0,0 +1,106 @@
+#!/bin/bash
+
+
+# THE FINAL SOLUTION
+# DEPENDENCIES: sox, cmus
+# ANOTHER OPTION WOULD BE TO DMENU ALL THE OPTIONS, SO IT CAN BE RUN WITH ONLY ONE KEYBOARD SHORTCUT
+# AND I CAN'T HELP BUT THINK THAT THERE'S SOME WAY TO UNIFY THE PLAYLIST TYPES AND THE SINGLE-SONG BRANCH AND USE ONLY FLAGS INSTEAD OF IF
+# ALSO IT WOULD PROBABLY RUN FASTER IF I REPLACED EVERY FIND WITH LOCATE
+# OH WELL
+
+# look into types of parentheses, $(( )) is math for example
+# shuf is a thing
+# also xargs -r is a thing
+
+
+[[ ! $(pgrep -x cmus) ]] && xfce4-terminal -e cmus && exit
+
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '------------------------------------------------------------------------------------------'
+ 'Play Song | play.sh | F8'
+ 'Queue Song | play.sh -q | F9'
+ 'Notify-send Queue | play.sh -n | F10'
+ 'Play/Pause Song | cmus-remote -u | Shift+F8'
+ 'Skip to Next Song | cmus-remote --next | Shift+F9'
+ 'Toggle Autoplay | cmus-remote -C "toggle continue" | Ctrl+F9'
+ 'Play Playlist | play.sh -l | Ctrl+Shift+F8'
+ 'Queue Playlist | play.sh -lq | Ctrl+Shift+F9'
+ 'Clear Playlist | play.sh -c | Ctrl+Shift+F10'
+ 'Increase Volume | cmus-remote -v +5% | Shift+Upper side mouse button'
+ 'Decrease Volume | cmus-remote -v -5% | Shift+Lower side mouse button'
+)
+
+playlist_flag=false
+queue_flag=false
+clear_flag=false
+current_song=$(cmus-remote -Q | grep file | sed "s|file ||g")
+
+MUSIC="$HOME/music"
+SONG=""
+SED_STRING="s|^|player-play |"
+DMENU_APPEARANCE="mononoki;11;#191919;#AAAAAA;#0000AA;#FFFFFF;Choose your jam:"
+
+#E0E04B
+#AAAA00
+
+while getopts "hnlqc" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send queue] [-l list] [-q queue] [-c clear]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+# n) notify-send -u low -t 15000 -i $HOME/stuf/rikanom.png "Songs currently in queue:" "$(cmus-remote -C 'save -q -' | sed -r 's|^\/([^\/]+\/)+||g' )"; exit ;; # NOTIFY-SEND QUEUE
+ n) $HOME/stuf/scripts/notification_wrapper.sh "\n> $( echo $current_song | sed -r 's|^\/([^\/]+\/)+||g' ) \n$( cmus-remote -C 'save -q -' | sed -r 's|^\/([^\/]+\/)+||g' | head -n 50 )" "UNIVPLAY" ; exit ;; # NOTIFY-SEND QUEUE VIA OWN WRAPPER
+ l) playlist_flag=true; DMENU_APPEARANCE="mononoki;11;#191919;#AAAAAA;#005000;#FFFFFF;Choose your playlist:" ;; # SET PLAYLIST FLAG, CHANGE DMENU COLOR
+ q) queue_flag=true; SED_STRING="s|^|add -q |"; DMENU_APPEARANCE="mononoki;11;#191919;#AAAA00;#0000AA;#FFFF00;Queue your jam:" ;; # SET QUEUE FLAG, CHANGE DMENU TEXT
+ c) clear_flag=true; DMENU_APPEARANCE="mononoki;11;#191919;#AAAAAA;#900D09;#FFFFFF;Choose queue clear method:" ;; # SET CLEAR FLAG, CHANGE DMENU COLOR/TEXT
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+
+for i in {1..7}
+do
+ declare "dm$i=$(echo $DMENU_APPEARANCE | cut -f$i -d';')" # CONVERT DMENU APPEARANCE STRIP INTO SEPARATE PARAMETERS
+done
+
+
+if $clear_flag ; then
+
+ CHOICE=$(echo -e "1. Keep only the currently playing song\n2. Clear everything\n3. Abort" \
+ | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "$dm7" )
+ echo "$CHOICE" | grep "1" &> /dev/null && cmus-remote -q -c
+ # fuck you cmus
+ echo "$CHOICE" | grep "2" &> /dev/null && cmus-remote -q -c && sox -n -r 44100 -c 2 /tmp/silence.wav trim 0.0 0.5 && cmus-remote -C "player-play /tmp/silence.wav" && rm /tmp/silence.wav && cmus-remote -C "set continue=false"
+ exit
+fi
+
+
+
+
+if $playlist_flag ; then
+
+ # FIND EVERY DIRECTORY WITH AN AUDIO FILE IN IT AND PIPE IT INTO DMENU
+ PL_DIR=$(find $MUSIC -type f -iregex ".*\.\(mp3\|flac\|m4a\|ogg\)$" | sed -r "s|([^\/])+$||g" | sort --version-sort | uniq \
+ | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "$dm7" )
+
+ if [[ -n $PL_DIR ]]; then
+
+ $queue_flag || cmus-remote -q -c # || OPERATOR; IF QUEUE FLAG IS FALSE, THEN CLEAR THE QUEUE
+ find "$PL_DIR" -maxdepth 1 -type f | sort | sed "s|^|add -q |" | sed -r "s|'|\\\'|g" | xargs -I{} cmus-remote -C "{}" # FIND EVERY SONG IN THE DIRECTORY (IGNORING ANY NESTED DIRECTORIES) AND PIPE THEM INTO CMUS-REMOTE
+ $queue_flag || cmus-remote -q --next # || OPERATOR; IF QUEUE FLAG IS FALSE, PUSH THE FIRST-QUEUED SONG
+ $queue_flag || cmus-remote -p # || OPERATOR; IF QUEUE FLAG IS FALSE, PLAY THE PUSHED SONG
+ cmus-remote -C "set continue=true" # ENABLE AUTOPLAY
+ cmus-remote -C "set play_library=false" # DISABLE ORDINARY PLAYLIST PLAYBACK JUST IN CASE
+ fi
+
+else
+
+ # FIND EVERY AUDIO FILE AND PIPE IT INTO DMENU
+ SONG=$(find $MUSIC -type f -iregex ".*\.\(mp3\|flac\|m4a\|ogg\)$" | sed -r "s|^\/([^\/]+\/)+||g" | sort --version-sort \
+ | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "$dm7" )
+
+ [[ -n $SONG ]] && [[ -n "$current_song" ]] && ! $queue_flag && cmus-remote -C "add -Q $current_song" # IF QUEUE FLAG IS FALSE, PLACE CURRENTLY PLAYING SONG AT THE END OF THE QUEUE
+ [[ -n $SONG ]] && echo $SONG | sed -r "s|'|\\\'|g" | xargs -I{} find $MUSIC -type f -iname "{}" | head -n 1 | sed "$SED_STRING" | cmus-remote # FIND THE CLEANED-UP SONG'S FULL PATH AND PIPE IT INTO CMUS-REMOTE
+ [[ -n $SONG ]] && cmus-remote -C "set continue=$queue_flag" # TOGGLE AUTOPLAY BASED ON CIRCUMSTANCES
+
+fi \ No newline at end of file
diff --git a/miscripts/wireguard-install.sh b/miscripts/wireguard-install.sh
new file mode 100644
index 0000000..22ca7cc
--- /dev/null
+++ b/miscripts/wireguard-install.sh
@@ -0,0 +1,474 @@
+#!/bin/bash
+
+# Secure WireGuard server installer
+# https://github.com/angristan/wireguard-install
+
+RED='\033[0;31m'
+ORANGE='\033[0;33m'
+NC='\033[0m'
+
+function isRoot() {
+ if [ "${EUID}" -ne 0 ]; then
+ echo "You need to run this script as root"
+ exit 1
+ fi
+}
+
+function checkVirt() {
+ if [ "$(systemd-detect-virt)" == "openvz" ]; then
+ echo "OpenVZ is not supported"
+ exit 1
+ fi
+
+ if [ "$(systemd-detect-virt)" == "lxc" ]; then
+ echo "LXC is not supported (yet)."
+ echo "WireGuard can technically run in an LXC container,"
+ echo "but the kernel module has to be installed on the host,"
+ echo "the container has to be run with some specific parameters"
+ echo "and only the tools need to be installed in the container."
+ exit 1
+ fi
+}
+
+function checkOS() {
+ # Check OS version
+ if [[ -e /etc/debian_version ]]; then
+ source /etc/os-release
+ OS="${ID}" # debian or ubuntu
+ if [[ ${ID} == "debian" || ${ID} == "raspbian" ]]; then
+ if [[ ${VERSION_ID} -lt 10 ]]; then
+ echo "Your version of Debian (${VERSION_ID}) is not supported. Please use Debian 10 Buster or later"
+ exit 1
+ fi
+ OS=debian # overwrite if raspbian
+ fi
+ elif [[ -e /etc/rocky-release ]]; then
+ source /etc/os-release
+ OS=almalinux
+ elif [[ -e /etc/almalinux-release ]]; then
+ source /etc/os-release
+ os=almalinux
+ elif [[ -e /etc/fedora-release ]]; then
+ source /etc/os-release
+ OS="${ID}"
+ elif [[ -e /etc/centos-release ]]; then
+ source /etc/os-release
+ OS=centos
+ elif [[ -e /etc/oracle-release ]]; then
+ source /etc/os-release
+ OS=oracle
+ elif [[ -e /etc/arch-release ]]; then
+ OS=arch
+ else
+ echo "Looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, AlmaLinux, Oracle or Arch Linux system"
+ exit 1
+ fi
+}
+
+function initialCheck() {
+ isRoot
+ checkVirt
+ checkOS
+}
+
+function installQuestions() {
+ echo "Welcome to the WireGuard installer!"
+ echo "The git repository is available at: https://github.com/angristan/wireguard-install"
+ echo ""
+ echo "I need to ask you a few questions before starting the setup."
+ echo "You can leave the default options and just press enter if you are ok with them."
+ echo ""
+
+ # Detect public IPv4 or IPv6 address and pre-fill for the user
+ SERVER_PUB_IP=$(ip -4 addr | sed -ne 's|^.* inet \([^/]*\)/.* scope global.*$|\1|p' | awk '{print $1}' | head -1)
+ if [[ -z ${SERVER_PUB_IP} ]]; then
+ # Detect public IPv6 address
+ SERVER_PUB_IP=$(ip -6 addr | sed -ne 's|^.* inet6 \([^/]*\)/.* scope global.*$|\1|p' | head -1)
+ fi
+ read -rp "IPv4 or IPv6 public address: " -e -i "${SERVER_PUB_IP}" SERVER_PUB_IP
+
+ # Detect public interface and pre-fill for the user
+ SERVER_NIC="$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)' | head -1)"
+ until [[ ${SERVER_PUB_NIC} =~ ^[a-zA-Z0-9_]+$ ]]; do
+ read -rp "Public interface: " -e -i "${SERVER_NIC}" SERVER_PUB_NIC
+ done
+
+ until [[ ${SERVER_WG_NIC} =~ ^[a-zA-Z0-9_]+$ && ${#SERVER_WG_NIC} -lt 16 ]]; do
+ read -rp "WireGuard interface name: " -e -i wg0 SERVER_WG_NIC
+ done
+
+ until [[ ${SERVER_WG_IPV4} =~ ^([0-9]{1,3}\.){3} ]]; do
+ read -rp "Server's WireGuard IPv4: " -e -i 10.66.66.1 SERVER_WG_IPV4
+ done
+
+ until [[ ${SERVER_WG_IPV6} =~ ^([a-f0-9]{1,4}:){3,4}: ]]; do
+ read -rp "Server's WireGuard IPv6: " -e -i fd42:42:42::1 SERVER_WG_IPV6
+ done
+
+ # Generate random number within private ports range
+ RANDOM_PORT=$(shuf -i49152-65535 -n1)
+ until [[ ${SERVER_PORT} =~ ^[0-9]+$ ]] && [ "${SERVER_PORT}" -ge 1 ] && [ "${SERVER_PORT}" -le 65535 ]; do
+ read -rp "Server's WireGuard port [1-65535]: " -e -i "${RANDOM_PORT}" SERVER_PORT
+ done
+
+ # Adguard DNS by default
+ until [[ ${CLIENT_DNS_1} =~ ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ ]]; do
+ read -rp "First DNS resolver to use for the clients: " -e -i 94.140.14.14 CLIENT_DNS_1
+ done
+ until [[ ${CLIENT_DNS_2} =~ ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ ]]; do
+ read -rp "Second DNS resolver to use for the clients (optional): " -e -i 94.140.15.15 CLIENT_DNS_2
+ if [[ ${CLIENT_DNS_2} == "" ]]; then
+ CLIENT_DNS_2="${CLIENT_DNS_1}"
+ fi
+ done
+
+ echo ""
+ echo "Okay, that was all I needed. We are ready to setup your WireGuard server now."
+ echo "You will be able to generate a client at the end of the installation."
+ read -n1 -r -p "Press any key to continue..."
+}
+
+function installWireGuard() {
+ # Run setup questions first
+ installQuestions
+
+ # Install WireGuard tools and module
+ if [[ ${OS} == 'ubuntu' ]] || [[ ${OS} == 'debian' && ${VERSION_ID} -gt 10 ]]; then
+ apt-get update
+ apt-get install -y wireguard iptables resolvconf qrencode
+ elif [[ ${OS} == 'debian' ]]; then
+ if ! grep -rqs "^deb .* buster-backports" /etc/apt/; then
+ echo "deb http://deb.debian.org/debian buster-backports main" >/etc/apt/sources.list.d/backports.list
+ apt-get update
+ fi
+ apt update
+ apt-get install -y iptables resolvconf qrencode
+ apt-get install -y -t buster-backports wireguard
+ elif [[ ${OS} == 'fedora' ]]; then
+ if [[ ${VERSION_ID} -lt 32 ]]; then
+ dnf install -y dnf-plugins-core
+ dnf copr enable -y jdoss/wireguard
+ dnf install -y wireguard-dkms
+ fi
+ dnf install -y wireguard-tools iptables qrencode
+ elif [[ ${OS} == 'almalinux' ]]; then
+ dnf -y install epel-release elrepo-release
+ dnf -y install wireguard-tools iptables qrencode
+ if [[ ${VERSION_ID} == 8* ]]; then
+ dnf -y install kmod-wireguard
+ fi
+ elif [[ ${OS} == 'centos' ]]; then
+ yum -y install epel-release elrepo-release
+ if [[ ${VERSION_ID} -eq 7 ]]; then
+ yum -y install yum-plugin-elrepo
+ fi
+ yum -y install kmod-wireguard wireguard-tools iptables qrencode
+ elif [[ ${OS} == 'oracle' ]]; then
+ dnf install -y oraclelinux-developer-release-el8
+ dnf config-manager --disable -y ol8_developer
+ dnf config-manager --enable -y ol8_developer_UEKR6
+ dnf config-manager --save -y --setopt=ol8_developer_UEKR6.includepkgs='wireguard-tools*'
+ dnf install -y wireguard-tools qrencode iptables
+ elif [[ ${OS} == 'arch' ]]; then
+ pacman -S --needed --noconfirm wireguard-tools qrencode
+ fi
+
+ # Make sure the directory exists (this does not seem the be the case on fedora)
+ mkdir /etc/wireguard >/dev/null 2>&1
+
+ chmod 600 -R /etc/wireguard/
+
+ SERVER_PRIV_KEY=$(wg genkey)
+ SERVER_PUB_KEY=$(echo "${SERVER_PRIV_KEY}" | wg pubkey)
+
+ # Save WireGuard settings
+ echo "SERVER_PUB_IP=${SERVER_PUB_IP}
+SERVER_PUB_NIC=${SERVER_PUB_NIC}
+SERVER_WG_NIC=${SERVER_WG_NIC}
+SERVER_WG_IPV4=${SERVER_WG_IPV4}
+SERVER_WG_IPV6=${SERVER_WG_IPV6}
+SERVER_PORT=${SERVER_PORT}
+SERVER_PRIV_KEY=${SERVER_PRIV_KEY}
+SERVER_PUB_KEY=${SERVER_PUB_KEY}
+CLIENT_DNS_1=${CLIENT_DNS_1}
+CLIENT_DNS_2=${CLIENT_DNS_2}" >/etc/wireguard/params
+
+ # Add server interface
+ echo "[Interface]
+Address = ${SERVER_WG_IPV4}/24,${SERVER_WG_IPV6}/64
+ListenPort = ${SERVER_PORT}
+PrivateKey = ${SERVER_PRIV_KEY}" >"/etc/wireguard/${SERVER_WG_NIC}.conf"
+
+ if pgrep firewalld; then
+ FIREWALLD_IPV4_ADDRESS=$(echo "${SERVER_WG_IPV4}" | cut -d"." -f1-3)".0"
+ FIREWALLD_IPV6_ADDRESS=$(echo "${SERVER_WG_IPV6}" | sed 's/:[^:]*$/:0/')
+ echo "PostUp = firewall-cmd --add-port ${SERVER_PORT}/udp && firewall-cmd --add-rich-rule='rule family=ipv4 source address=${FIREWALLD_IPV4_ADDRESS}/24 masquerade' && firewall-cmd --add-rich-rule='rule family=ipv6 source address=${FIREWALLD_IPV6_ADDRESS}/24 masquerade'
+PostDown = firewall-cmd --remove-port ${SERVER_PORT}/udp && firewall-cmd --remove-rich-rule='rule family=ipv4 source address=${FIREWALLD_IPV4_ADDRESS}/24 masquerade' && firewall-cmd --remove-rich-rule='rule family=ipv6 source address=${FIREWALLD_IPV6_ADDRESS}/24 masquerade'" >>"/etc/wireguard/${SERVER_WG_NIC}.conf"
+ else
+ echo "PostUp = iptables -A FORWARD -i ${SERVER_PUB_NIC} -o ${SERVER_WG_NIC} -j ACCEPT; iptables -A FORWARD -i ${SERVER_WG_NIC} -j ACCEPT; iptables -t nat -A POSTROUTING -o ${SERVER_PUB_NIC} -j MASQUERADE; ip6tables -A FORWARD -i ${SERVER_WG_NIC} -j ACCEPT; ip6tables -t nat -A POSTROUTING -o ${SERVER_PUB_NIC} -j MASQUERADE
+PostDown = iptables -D FORWARD -i ${SERVER_PUB_NIC} -o ${SERVER_WG_NIC} -j ACCEPT; iptables -D FORWARD -i ${SERVER_WG_NIC} -j ACCEPT; iptables -t nat -D POSTROUTING -o ${SERVER_PUB_NIC} -j MASQUERADE; ip6tables -D FORWARD -i ${SERVER_WG_NIC} -j ACCEPT; ip6tables -t nat -D POSTROUTING -o ${SERVER_PUB_NIC} -j MASQUERADE" >>"/etc/wireguard/${SERVER_WG_NIC}.conf"
+ fi
+
+ # Enable routing on the server
+ echo "net.ipv4.ip_forward = 1
+net.ipv6.conf.all.forwarding = 1" >/etc/sysctl.d/wg.conf
+
+ sysctl --system
+
+ systemctl start "wg-quick@${SERVER_WG_NIC}"
+ systemctl enable "wg-quick@${SERVER_WG_NIC}"
+
+ newClient
+ echo "If you want to add more clients, you simply need to run this script another time!"
+
+ # Check if WireGuard is running
+ systemctl is-active --quiet "wg-quick@${SERVER_WG_NIC}"
+ WG_RUNNING=$?
+
+ # WireGuard might not work if we updated the kernel. Tell the user to reboot
+ if [[ ${WG_RUNNING} -ne 0 ]]; then
+ echo -e "\n${RED}WARNING: WireGuard does not seem to be running.${NC}"
+ echo -e "${ORANGE}You can check if WireGuard is running with: systemctl status wg-quick@${SERVER_WG_NIC}${NC}"
+ echo -e "${ORANGE}If you get something like \"Cannot find device ${SERVER_WG_NIC}\", please reboot!${NC}"
+ fi
+}
+
+function newClient() {
+ ENDPOINT="${SERVER_PUB_IP}:${SERVER_PORT}"
+
+ echo ""
+ echo "Tell me a name for the client."
+ echo "The name must consist of alphanumeric character. It may also include an underscore or a dash and can't exceed 15 chars."
+
+ until [[ ${CLIENT_NAME} =~ ^[a-zA-Z0-9_-]+$ && ${CLIENT_EXISTS} == '0' && ${#CLIENT_NAME} -lt 16 ]]; do
+ read -rp "Client name: " -e CLIENT_NAME
+ CLIENT_EXISTS=$(grep -c -E "^### Client ${CLIENT_NAME}\$" "/etc/wireguard/${SERVER_WG_NIC}.conf")
+
+ if [[ ${CLIENT_EXISTS} == '1' ]]; then
+ echo ""
+ echo "A client with the specified name was already created, please choose another name."
+ echo ""
+ fi
+ done
+
+ for DOT_IP in {2..254}; do
+ DOT_EXISTS=$(grep -c "${SERVER_WG_IPV4::-1}${DOT_IP}" "/etc/wireguard/${SERVER_WG_NIC}.conf")
+ if [[ ${DOT_EXISTS} == '0' ]]; then
+ break
+ fi
+ done
+
+ if [[ ${DOT_EXISTS} == '1' ]]; then
+ echo ""
+ echo "The subnet configured supports only 253 clients."
+ exit 1
+ fi
+
+ BASE_IP=$(echo "$SERVER_WG_IPV4" | awk -F '.' '{ print $1"."$2"."$3 }')
+ until [[ ${IPV4_EXISTS} == '0' ]]; do
+ read -rp "Client's WireGuard IPv4: ${BASE_IP}." -e -i "${DOT_IP}" DOT_IP
+ CLIENT_WG_IPV4="${BASE_IP}.${DOT_IP}"
+ IPV4_EXISTS=$(grep -c "$CLIENT_WG_IPV4/24" "/etc/wireguard/${SERVER_WG_NIC}.conf")
+
+ if [[ ${IPV4_EXISTS} == '1' ]]; then
+ echo ""
+ echo "A client with the specified IPv4 was already created, please choose another IPv4."
+ echo ""
+ fi
+ done
+
+ BASE_IP=$(echo "$SERVER_WG_IPV6" | awk -F '::' '{ print $1 }')
+ until [[ ${IPV6_EXISTS} == '0' ]]; do
+ read -rp "Client's WireGuard IPv6: ${BASE_IP}::" -e -i "${DOT_IP}" DOT_IP
+ CLIENT_WG_IPV6="${BASE_IP}::${DOT_IP}"
+ IPV6_EXISTS=$(grep -c "${CLIENT_WG_IPV6}/64" "/etc/wireguard/${SERVER_WG_NIC}.conf")
+
+ if [[ ${IPV6_EXISTS} == '1' ]]; then
+ echo ""
+ echo "A client with the specified IPv6 was already created, please choose another IPv6."
+ echo ""
+ fi
+ done
+
+ # Generate key pair for the client
+ CLIENT_PRIV_KEY=$(wg genkey)
+ CLIENT_PUB_KEY=$(echo "${CLIENT_PRIV_KEY}" | wg pubkey)
+ CLIENT_PRE_SHARED_KEY=$(wg genpsk)
+
+ # Home directory of the user, where the client configuration will be written
+ if [ -e "/home/${CLIENT_NAME}" ]; then
+ # if $1 is a user name
+ HOME_DIR="/home/${CLIENT_NAME}"
+ elif [ "${SUDO_USER}" ]; then
+ # if not, use SUDO_USER
+ if [ "${SUDO_USER}" == "root" ]; then
+ # If running sudo as root
+ HOME_DIR="/root"
+ else
+ HOME_DIR="/home/${SUDO_USER}"
+ fi
+ else
+ # if not SUDO_USER, use /root
+ HOME_DIR="/root"
+ fi
+
+ # Create client file and add the server as a peer
+ echo "[Interface]
+PrivateKey = ${CLIENT_PRIV_KEY}
+Address = ${CLIENT_WG_IPV4}/32,${CLIENT_WG_IPV6}/128
+DNS = ${CLIENT_DNS_1},${CLIENT_DNS_2}
+
+[Peer]
+PublicKey = ${SERVER_PUB_KEY}
+PresharedKey = ${CLIENT_PRE_SHARED_KEY}
+Endpoint = ${ENDPOINT}
+AllowedIPs = 0.0.0.0/0,::/0" >>"${HOME_DIR}/${SERVER_WG_NIC}-client-${CLIENT_NAME}.conf"
+
+ # Add the client as a peer to the server
+ echo -e "\n### Client ${CLIENT_NAME}
+[Peer]
+PublicKey = ${CLIENT_PUB_KEY}
+PresharedKey = ${CLIENT_PRE_SHARED_KEY}
+AllowedIPs = ${CLIENT_WG_IPV4}/32,${CLIENT_WG_IPV6}/128" >>"/etc/wireguard/${SERVER_WG_NIC}.conf"
+
+ wg syncconf "${SERVER_WG_NIC}" <(wg-quick strip "${SERVER_WG_NIC}")
+
+ echo -e "\nHere is your client config file as a QR Code:"
+
+ qrencode -t ansiutf8 -l L <"${HOME_DIR}/${SERVER_WG_NIC}-client-${CLIENT_NAME}.conf"
+
+ echo "It is also available in ${HOME_DIR}/${SERVER_WG_NIC}-client-${CLIENT_NAME}.conf"
+}
+
+function revokeClient() {
+ NUMBER_OF_CLIENTS=$(grep -c -E "^### Client" "/etc/wireguard/${SERVER_WG_NIC}.conf")
+ if [[ ${NUMBER_OF_CLIENTS} == '0' ]]; then
+ echo ""
+ echo "You have no existing clients!"
+ exit 1
+ fi
+
+ echo ""
+ echo "Select the existing client you want to revoke"
+ grep -E "^### Client" "/etc/wireguard/${SERVER_WG_NIC}.conf" | cut -d ' ' -f 3 | nl -s ') '
+ until [[ ${CLIENT_NUMBER} -ge 1 && ${CLIENT_NUMBER} -le ${NUMBER_OF_CLIENTS} ]]; do
+ if [[ ${CLIENT_NUMBER} == '1' ]]; then
+ read -rp "Select one client [1]: " CLIENT_NUMBER
+ else
+ read -rp "Select one client [1-${NUMBER_OF_CLIENTS}]: " CLIENT_NUMBER
+ fi
+ done
+
+ # match the selected number to a client name
+ CLIENT_NAME=$(grep -E "^### Client" "/etc/wireguard/${SERVER_WG_NIC}.conf" | cut -d ' ' -f 3 | sed -n "${CLIENT_NUMBER}"p)
+
+ # remove [Peer] block matching $CLIENT_NAME
+ sed -i "/^### Client ${CLIENT_NAME}\$/,/^$/d" "/etc/wireguard/${SERVER_WG_NIC}.conf"
+
+ # remove generated client file
+ rm -f "${HOME}/${SERVER_WG_NIC}-client-${CLIENT_NAME}.conf"
+
+ # restart wireguard to apply changes
+ wg syncconf "${SERVER_WG_NIC}" <(wg-quick strip "${SERVER_WG_NIC}")
+}
+
+function uninstallWg() {
+ echo ""
+ read -rp "Do you really want to remove WireGuard? [y/n]: " -e REMOVE
+ REMOVE=${REMOVE:-n}
+ if [[ $REMOVE == 'y' ]]; then
+ checkOS
+
+ systemctl stop "wg-quick@${SERVER_WG_NIC}"
+ systemctl disable "wg-quick@${SERVER_WG_NIC}"
+
+ if [[ ${OS} == 'ubuntu' ]]; then
+ apt-get autoremove --purge -y wireguard qrencode
+ elif [[ ${OS} == 'debian' ]]; then
+ apt-get autoremove --purge -y wireguard qrencode
+ elif [[ ${OS} == 'fedora' ]]; then
+ dnf remove -y wireguard-tools qrencode
+ if [[ ${VERSION_ID} -lt 32 ]]; then
+ dnf remove -y wireguard-dkms
+ dnf copr disable -y jdoss/wireguard
+ fi
+ dnf autoremove -y
+ elif [[ ${OS} == 'almalinux' ]]; then
+ dnf -y remove wireguard-tools qrencode
+ if [[ ${VERSION_ID} == 8* ]]; then
+ dnf -y remove kmod-wireguard
+ fi
+ dnf -y autoremove
+ elif [[ ${OS} == 'centos' ]]; then
+ yum -y remove kmod-wireguard wireguard-tools qrencode
+ yum -y autoremove
+ elif [[ ${OS} == 'oracle' ]]; then
+ yum -y remove wireguard-tools qrencode
+ yum -y autoremove
+ elif [[ ${OS} == 'arch' ]]; then
+ pacman -Rs --noconfirm wireguard-tools qrencode
+ fi
+
+ rm -rf /etc/wireguard
+ rm -f /etc/sysctl.d/wg.conf
+
+ # Reload sysctl
+ sysctl --system
+
+ # Check if WireGuard is running
+ systemctl is-active --quiet "wg-quick@${SERVER_WG_NIC}"
+ WG_RUNNING=$?
+
+ if [[ ${WG_RUNNING} -eq 0 ]]; then
+ echo "WireGuard failed to uninstall properly."
+ exit 1
+ else
+ echo "WireGuard uninstalled successfully."
+ exit 0
+ fi
+ else
+ echo ""
+ echo "Removal aborted!"
+ fi
+}
+
+function manageMenu() {
+ echo "Welcome to WireGuard-install!"
+ echo "The git repository is available at: https://github.com/angristan/wireguard-install"
+ echo ""
+ echo "It looks like WireGuard is already installed."
+ echo ""
+ echo "What do you want to do?"
+ echo " 1) Add a new user"
+ echo " 2) Revoke existing user"
+ echo " 3) Uninstall WireGuard"
+ echo " 4) Exit"
+ until [[ ${MENU_OPTION} =~ ^[1-4]$ ]]; do
+ read -rp "Select an option [1-4]: " MENU_OPTION
+ done
+ case "${MENU_OPTION}" in
+ 1)
+ newClient
+ ;;
+ 2)
+ revokeClient
+ ;;
+ 3)
+ uninstallWg
+ ;;
+ 4)
+ exit 0
+ ;;
+ esac
+}
+
+# Check for root, virt, OS...
+initialCheck
+
+# Check if WireGuard is already installed and load params
+if [[ -e /etc/wireguard/params ]]; then
+ source /etc/wireguard/params
+ manageMenu
+else
+ installWireGuard
+fi
diff --git a/musicbot_wrapper.sh b/musicbot_wrapper.sh
new file mode 100755
index 0000000..6876c9a
--- /dev/null
+++ b/musicbot_wrapper.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+if [ $# -eq 0 ]; then
+ echo "You need to provide a music directory as the first argument"
+ exit 1
+fi
+
+[[ -z "$(pactl list short modules | grep "DiscordSink")" ]] && /bin/bash $HOME/stuf/scripts/minivac.sh -l
+firstfile=$(find "$(realpath "$1")" -type f -iregex ".*\.\(mp3\|flac\|m4a\|ogg\)$" | sort | head -n 1)
+
+if [ -z "$firstfile" ]; then
+ echo "Could not find any music files in given directory"
+ exit 1
+fi
+
+pgrep -f "MPV Local Music Bot" >/dev/null 2>&1 && pkill -f "MPV Local Music Bot"
+nohup /usr/bin/mpv --title="MPV Local Music Bot" --force-window=yes --geometry=750x750+710+290 --volume=80 --loop-file=inf --af="loudnorm=I=-25:TP=-1.5:LRA=1" "$firstfile" >/dev/null 2>&1 &
+while [[ $window == "" || $window == *$'\n'* ]]; do window=$(xdotool search --name "MPV Local Music Bot"); done
+sleep 0.5 && xdotool key --window "$window" Control_L+o
+
+#MPV WINDOW / SOME OTHER OUTPUT
+sinkinput=$(pactl list sink-inputs | grep -E "^\s*Sink\ Input\ \#|^\s*media\.name\ \=\ " | tac | grep -A 1 -E "^\s*media\.name\ \=\ .*MPV\ Local\ Music\ Bot.*" | grep -oP "Sink\ Input\ \#\K[0-9]+")
+
+#SINK
+#sink=$(pacmd list-sinks | grep -E "^\s*name:|^\s*module:" | grep -A 1 -E "^\s*name: <CombinedDiscordSink>" | grep -oP "module: \K[0-9]+")
+#IS HOW IT WAS SUPPOSED TO BE, BUT
+sink="CombinedDiscordSink"
+#IS ENOUGH
+#SET THE SINK TO alsa_output.pci-0000_03_04.0.analog-stereo TO PUT IT BACK
+
+pactl move-sink-input "$sinkinput" "$sink"
+
+#--af-add='dynaudnorm=g=5:f=250:r=0.9:p=0.5' \ No newline at end of file
diff --git a/notification_wrapper.sh b/notification_wrapper.sh
new file mode 100755
index 0000000..91a7b07
--- /dev/null
+++ b/notification_wrapper.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+# echo "This is $0"
+# echo "This is \$BASH_SOURCE: $BASH_SOURCE"
+
+export DISPLAY=":0.0"
+export XDG_RUNTIME_DIR=/run/user/$(id -u)
+
+##------------------------------DEFAULT NOTIFICATION------------------------------##
+
+FLAG=true
+CALLER=$( ps -o comm= $PID )
+SUMMARY="Default Notification - CALLER:$CALLER "
+ICONS="$HOME/stuf/customization/icons"
+ICON="$ICONS/ENE.png"
+TIME=5000
+
+##------------------------------ECHO NOTIFICATION------------------------------##
+
+#DOESN'T WORK, FIGURE OUT WHY ONE DAY
+
+[[ $( grep "echo" <<< "$CALLER" ) ]] && SUMMARY="Echo" && DESCRIPTION=$1 && ICON="$ICONS/ENE.png"
+
+##------------------------------RSYNC NOTIFICATION------------------------------##
+
+[[ $( grep "wide_rsync" <<< "$CALLER" ) && "$2" =~ "WRSYNC" ]] && SUMMARY="Wide Rsync" && DESCRIPTION=$1 && TIME=15000 && [[ "$2" == "WRSYNCSUCC" ]] && ICON="$ICONS/yayoiooo.png" || ICON="$ICONS/yayoiclose.png"
+
+##------------------------------KEYMAPPER NOTIFICATION------------------------------##
+
+[[ $( grep "keymap_toggler" <<< "$CALLER" ) && "$2" == "KEYTOG" ]] && SUMMARY="Key Mapper" && DESCRIPTION=$1 && ICON="$ICONS/erikahorni.png"
+
+##------------------------------DISCORD MEDIA STREAMER NOTIFICATION------------------------------##
+
+[[ $( grep "minivac" <<< "$CALLER" ) && "$2" == "MINIVAC" ]] && SUMMARY="MiniVAC" && DESCRIPTION=$1 && TIME=15000 && ICON="$ICONS/holic.gif"
+
+##------------------------------VOICE CHANGER NOTIFICATION------------------------------##
+
+[[ $( grep "voice_changer" <<< "$CALLER" ) && "$2" == "VOICHANG" ]] && SUMMARY="Voice Changer" && DESCRIPTION=$1 && TIME=15000 && ICON="$ICONS/holic.gif"
+
+##------------------------------NEWSBOAT NOTIFICATION------------------------------##
+
+[[ $( grep "newsboat" <<< "$CALLER" ) ]] && SUMMARY="Newsboat" && DESCRIPTION=$1 && ICON="$ICONS/kizukawablob.png"
+
+##------------------------------FIND LAST NOTIFICATION------------------------------##
+[[ $( grep "findlast" <<< "$CALLER" ) && "$2" == "FINDLAST" ]] && SUMMARY="Find last filename" && DESCRIPTION=$1 && ICON="$ICONS/shupogaki.png"
+
+##------------------------------DL HELPER (CLIPBOARD VER.) NOTIFICATION------------------------------##
+if [[ $( grep "dl_helper" <<< "$CALLER" ) && "$2" == "DLHELPER" ]]; then
+ SUMMARY="DL Helper (clipboard ver.)" && DESCRIPTION=$()
+ [[ $1 -eq 0 ]] && { DESCRIPTION="Copied link successfully dl'd" ; ICON="$ICONS/rhodeslogo.png" ; } || { DESCRIPTION="ERROR - Couldn't dl copied link" ; ICON="$ICONS/rhodeslogo.png" ; }
+ #CHANGE ICON
+fi
+
+##------------------------------CMUS NOTIFICATION------------------------------##
+
+if [[ $( grep cmus <<< "$CALLER" ) ]]; then
+
+ SUMMARY="Cmus"
+
+
+ # ARGUMENT PROCESSING
+
+ while test $# -ge 2
+ do
+ eval _$1='$2'
+ shift
+ shift
+ done
+
+
+ # ARGUMENT FORMATTING
+ ### for some reason processing $_duration of certain files completely erases all the other args,
+ ### so the three lines below have to be above it
+ ### echo $@ if you don't believe me
+
+
+ # ${} Parameter expansion
+ # $() Command substitution
+
+ [[ $_artist == "" ]] && _artist="N/A"
+ [[ $_title == "" ]] && [[ ${_title=$_file} == "" ]] && _title="N/A"
+ [[ $_status != *playing* ]] && FLAG=0
+
+ # DURATION FORMATTING
+
+ if [[ $_duration != "" ]]; then
+ h=$(($_duration / 3600))
+ m=$(($_duration % 3600))
+
+ duration=""
+ test $h -gt 0 && dur="$h:"
+ duration="$dur$(printf '%02d:%02d' $(($m / 60)) $(($m % 60)))"
+ else
+ duration="N/A"
+ fi
+
+
+ # DESCRIPTION FORMATTING
+ ICON="$ICONS/rikanom.png"
+ DESCRIPTION="Now playing: "$_title" ["$duration"]\nArtist: "$_artist
+fi
+
+##------------------------------PLAY SCRIPT QUEUE NOTIFICATION------------------------------##
+
+[[ $( grep "wide_play" <<< "$CALLER" ) && "$2" == "WIDEPLAY" ]] && SUMMARY="Songs currently in queue:" && DESCRIPTION=$1 && ICON="$ICONS/rikanom.png"
+
+##------------------------------ROTATION DAEMON NOTIFICATION------------------------------##
+
+if [[ $( grep "wide_rotat" <<< "$CALLER" ) && "$2" == "WIDEROT" ]]; then
+
+ OUTPUT=$1
+ ICON="$ICONS/tenshi1.png"
+ SUMMARY="Pape"
+ DESCRIPTION=""
+ [[ $( grep -i "killed" <<< "$OUTPUT" ) ]] && DESCRIPTION="Pape rotation daemon killed (PID: " || DESCRIPTION="Pape rotation daemon spawned (PID: " && DESCRIPTION="${DESCRIPTION}$(echo "$OUTPUT" | head -n 1 | sed -r "s/[^0-9]*//g"))"
+ [[ $( grep -i "bmp\|gif\|jpg\|jpeg\|png" <<< "$OUTPUT" ) ]] && DESCRIPTION="Current Pape: $OUTPUT"
+
+fi
+
+##------------------------------CLIPBOARD TREATER DAEMON NOTIFICATION------------------------------##
+
+if [[ $( grep clipboard_treat <<< "$CALLER" ) && "$2" == "CLIPTREAT" ]]; then
+
+ OUTPUT=$1
+ ICON="$ICONS/tenshi2.png"
+ SUMMARY="Clipboard Treater $2"
+ DESCRIPTION=""
+ [[ $( grep -i "killed" <<< "$OUTPUT" ) ]] && DESCRIPTION="Clipboard treater daemon killed (PID: " || DESCRIPTION="Clipboard treater daemon spawned (PID: " && DESCRIPTION="${DESCRIPTION}$(echo "$OUTPUT" | head -n 1 | sed -r "s/[^0-9]*//g"))"
+
+fi
+
+
+##------------------------------THE ACTUAL NOTIFICATION------------------------------##
+
+#notify-send -u low -t 5000 -i ~/stuf/tsubasatwinneofetch.jpg "$SUMMARY" "$DESCRIPTION"
+#notify-send -u low -t 5000 -i ~/stuf/red.jpg "$SUMMARY" "$1"
+$FLAG && notify-send -u low -t $TIME -i $ICON "$SUMMARY" "$DESCRIPTION"
+
+##------------------------------MAKE NOTIFICATIONS FOR OTHER SCRIPTS------------------------------##
+
+
diff --git a/nsxiv_utils/nsxiv-env b/nsxiv_utils/nsxiv-env
new file mode 100755
index 0000000..10eac20
--- /dev/null
+++ b/nsxiv_utils/nsxiv-env
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+NSXIV_OPTS=${NSXIV_OPTS:-"-a -q"}
+# set default args here ^ ^ inside the quotes
+
+exec nsxiv $NSXIV_OPTS "$@"
diff --git a/nsxiv_utils/nsxiv-pipe b/nsxiv_utils/nsxiv-pipe
new file mode 100755
index 0000000..31a5216
--- /dev/null
+++ b/nsxiv_utils/nsxiv-pipe
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+tmpfile="${TMPDIR:-/tmp}/nsxiv_pipe_$$"
+trap 'rm -f -- $tmpfile' EXIT
+
+if [ "$#" -eq 0 ]; then
+ if [ -t 0 ]; then
+ echo "nsxiv-pipe: No arguments provided" >&2; exit 1
+ else
+ # Consume stdin and put it in the temporal file
+ cat > "$tmpfile"
+ fi
+fi
+
+for arg in "$@"; do
+ # if it's a pipe then drain it to $tmpfile
+ [ -p "$arg" ] && cat "$arg" > "$tmpfile"
+done
+
+if [ -s "$tmpfile" ]; then
+ nsxiv -q "$@" "$tmpfile" # -q to silence warnings
+else
+ nsxiv "$@" # fallback
+fi
diff --git a/nsxiv_utils/nsxiv-rifle b/nsxiv_utils/nsxiv-rifle
new file mode 100755
index 0000000..ac50568
--- /dev/null
+++ b/nsxiv_utils/nsxiv-rifle
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+TMPDIR="${TMPDIR:-/tmp}"
+tmp="$TMPDIR/nsxiv_rifle_$$"
+
+is_img_extension() {
+ grep -iE '\.(jpe?g|png|gif|svg|jxl|webp|tiff|heif|avif|ico|bmp|pam|pbm|ppm|tga|qoi|ff)$'
+}
+
+listfiles() {
+ find -L "$1" -maxdepth 1 -type f -print |
+ is_img_extension | sort -V --ignore-case | tee "$tmp"
+}
+
+open_img() {
+ file="$1"; shift;
+ # only go through listfiles() if the file has a valid img extension
+ if echo "$file" | is_img_extension >/dev/null 2>&1; then
+ trap 'rm -f $tmp' EXIT
+ count="$(listfiles "///${file%/*}" | grep -nF "$file")"
+ fi
+ if [ -n "$count" ]; then
+ nsxiv -i -a -g 1200x900 -n "${count%%:*}" "$@" -- < "$tmp"
+ else
+ # fallback incase file didn't have a valid extension, or we couldn't
+ # find it inside the list
+ nsxiv -a -g 1200x900 "$@" -- "$file"
+ fi
+}
+
+uri2path() {
+ python3 - "$@" <<'___HEREDOC'
+from urllib.parse import unquote, urlparse
+from sys import argv
+for arg in argv[1:]:
+ print(unquote(urlparse(arg).path))
+___HEREDOC
+}
+
+[ "$1" = '--' ] && shift
+case "$1" in
+ "") echo "Usage: ${0##*/} PICTURES" >&2; exit 1 ;;
+ /*) open_img "$1" ;;
+ "~"/*) open_img "$HOME/${1#"~"/}" ;;
+ file:///*) open_img "$(uri2path "$1")" ;;
+ trash:///*)
+ trash_dir="${XDG_DATA_HOME:-$HOME/.local/share}/Trash/files"
+ open_img "${trash_dir}$(uri2path "$1")" -N "nsxiv_trash"
+ ;;
+ *) open_img "$PWD/$1" ;;
+esac
diff --git a/nsxiv_utils/nsxiv-url b/nsxiv_utils/nsxiv-url
new file mode 100755
index 0000000..575902a
--- /dev/null
+++ b/nsxiv_utils/nsxiv-url
@@ -0,0 +1,32 @@
+#!/usr/bin/env sh
+
+cache_dir="${TMPDIR:-/tmp}/nsxiv"
+
+die() {
+ [ -n "$1" ] && printf '%s\n' "$*" >&2;
+ exit 1
+}
+
+cleanup() {
+ rm -f -- "$cache_dir"/*
+}
+
+get_image() (
+ cd "$cache_dir" && curl -sSLO "$1"
+)
+
+### main ###
+
+[ -z "$1" ] && die "No arguments given"
+trap cleanup EXIT
+[ -d "$cache_dir" ] || mkdir -p -- "$cache_dir" || die
+while [ -n "$1" ]; do
+ case "$1" in
+ *://*.*) get_image "$1" ;;
+ *) echo "Invalid url: $1" >&2 ;;
+ esac
+ shift
+done
+
+[ "$(find "$cache_dir" -type f -print | wc -l)" -ne 0 ] &&
+ nsxiv -p "$cache_dir"
diff --git a/psd_converter.sh b/psd_converter.sh
new file mode 100755
index 0000000..5256613
--- /dev/null
+++ b/psd_converter.sh
@@ -0,0 +1,127 @@
+#!/bin/bash
+
+while getopts 'hi:' OPTION; do
+ case "$OPTION" in
+ h)
+ echo "you have supplied the -h option"
+ ;;
+ i)
+ variationsfile="$OPTARG"
+ echo "The variations file provided is $OPTARG"
+ ;;
+ ?)
+ echo "script usage: $(basename \$0) [-h] [-i variationsfile]" >&2
+ exit 1
+ ;;
+ esac
+done
+shift "$(($OPTIND -1))"
+
+file="$1"
+filename=$(basename -- "$1")
+filename="${filename%.*}"
+
+[[ -z $file ]] && echo "NO PSD FILE" && exit
+
+if [[ -n "$variationsfile" && -f "$variationsfile" ]]; then
+ [[ -z $( sed -n 1p "$variationsfile" | grep -E "^HEADER:(\s+[0-9]+)*\s*$" ) ]] && echo "WRONG FILE FORMAT (HEADER)" && exit
+ [[ -z $( sed -n 2p "$variationsfile" | grep -E "^FOOTER:(\s+[0-9]+)*\s*$" ) ]] && echo "WRONG FILE FORMAT (FOOTER)" && exit
+ [[ -n $( tail -n +3 "$variationsfile" | grep -E "^(\s+[0-9]+)+\s*$" ) ]] && echo "WRONG FILE FORMAT (VARIATIONS)" && exit
+fi
+
+breakdowndir="/tmp/psd_breakdown_$(echo $RANDOM | md5sum | head -c 5)"
+japanprofile="$HOME/stuf/scripts/dependencies/JapanColor2011Coated.icc"
+rgbprofile="$HOME/stuf/scripts/dependencies/GIMPsRGB.icc"
+
+largestgeometry=""
+largestsurface=0
+
+mkdir -p "$breakdowndir"
+touch "${breakdowndir}/geometries.txt"
+
+last=$( identify "$file" | tail -n 1 | sed -r "s|.*?\[([0-9]*)\].*?|\1|g" )
+needjapan=$( exiftool "$file" | grep -i "Japan Color 2001 Coated" | wc -l )
+
+echo "Number of parts: $last"
+
+for i in $(eval echo "{1..$last}"); do
+ geometry=$( identify "${file}[${i}]" | sed -r "s|^.*?\ ([0-9]+x[0-9]+[\+\-][0-9]+[\+\-][0-9]+)\ .*?$|\1|g" )
+ echo "Processing part ${i}..."
+ x="${geometry%x*}"
+ y="${geometry#*x}"
+ surface=$( bc <<< "${x} * ${y}" )
+ [[ $surface -gt $largestsurface ]] && largestsurface=$surface && largestgeometry=$geometry
+ if [[ needjapan -gt 0 ]]; then
+ convert "${file}[${i}]" -profile "${japanprofile}" -profile "${rgbprofile}" PNG32:"${breakdowndir}/${filename}_${i}_part.png" || break
+ else
+ convert "${file}[${i}]" -profile "${rgbprofile}" $composition PNG32:"${breakdowndir}/${filename}_${i}_part.png" || break
+ fi
+ echo "$i : ${geometry}" >> "${breakdowndir}/geometries.txt"
+done
+
+echo "Largest geometry: $largestgeometry"
+
+if [[ -n "$variationsfile" ]]; then
+
+ imagenum="$(wc -l < "$variationsfile")"
+ ((imagenum-=2))
+
+ echo "Final number of images: $imagenum"
+
+ i=0
+ for part in $( eval echo "$( sed -n 1p "$variationsfile" | sed -r "s|HEADER:||g" )" ) ; do
+ currentpart="${breakdowndir}/${filename}_${part}_part.png"
+
+ alpha="-matte -channel A +level 0,$( bc <<< "100 * $( identify -verbose ${filename}.psd[$part] | grep -i "opacity" | sed -r "s|^.*: ||g" ) / 65535" )% +channel"
+ composition="-compose $( identify -verbose ${filename}.psd[${part}] | grep -i "compose" | sed -r "s|.*: ||g" | tr '[:upper:]' '[:lower:]' )"
+
+ if [[ $i -eq 0 ]]; then
+ headerfile="${breakdowndir}/${filename}_header.png"
+ cp "$currentpart" "$headerfile"
+ else
+ geometry=$( grep "^$part : " "${breakdowndir}/geometries.txt" | cut -d ":" -f 2 | sed -r "s|\ +||g" )
+ convert "$headerfile" \( "$currentpart" $alpha \) -geometry "$geometry" $composition -composite "$headerfile"
+ fi
+ (( i++ ))
+ done
+
+ ((last=imagenum+2))
+ for sed_n in $( eval echo "{3..${last}}" ); do
+ ((finalnum=sed_n-2))
+ finalfile="$(pwd)/${filename}_${finalnum}.png"
+ cp "$headerfile" "$finalfile"
+ echo "Processing image ${finalnum}..."
+ for part in $( eval echo "$( sed -n ${sed_n}p "$variationsfile" ) $( sed -n 2p "$variationsfile" | sed -r "s|FOOTER:||g" )" ); do
+
+ alpha="-matte -channel A +level 0,$( bc <<< "100 * $( identify -verbose ${filename}.psd[$part] | grep -i "opacity" | sed -r "s|^.*: ||g" ) / 65535" )% +channel"
+ composition="-compose $( identify -verbose ${filename}.psd[${part}] | grep -i "compose" | sed -r "s|.*: ||g" | tr '[:upper:]' '[:lower:]' )"
+ echo "$composition"
+
+ [[ $part -eq -1 ]] && continue
+ currentpart="${breakdowndir}/${filename}_${part}_part.png"
+ geometry=$( grep "^$part : " "${breakdowndir}/geometries.txt" | cut -d ":" -f 2 | sed -r "s|\ +||g" )
+ convert "$finalfile" \( "$currentpart" $alpha \) -geometry "$geometry" $composition -composite "$finalfile"
+ done
+ done
+
+else
+
+ imagenum=$( grep "$largestgeometry" "${breakdowndir}/geometries.txt" | wc -l )
+ echo "Final number of images: $imagenum"
+
+ for i in $(eval echo "{1..$last}"); do
+ currentpart="${breakdowndir}/${filename}_${i}_part.png"
+ if [[ $( grep "^$i : $largestgeometry" "${breakdowndir}/geometries.txt" ) ]]; then
+ echo "Processing image ${imagenum}..."
+ currentfile="$(pwd)/${filename}_${imagenum}.png"
+ cp "$currentpart" "$currentfile"
+ (( imagenum-- ))
+ else
+ geometry=$( grep "^$i : " "${breakdowndir}/geometries.txt" | cut -d ":" -f 2 | sed -r "s|\ +||g" )
+ convert "$currentfile" \( "$currentpart" \) -geometry "$geometry" -composite "$currentfile"
+ fi
+ done
+
+fi
+
+rm -rf "$breakdowndir"
diff --git a/pyscripts/anilist_crawl.py b/pyscripts/anilist_crawl.py
new file mode 100755
index 0000000..c529343
--- /dev/null
+++ b/pyscripts/anilist_crawl.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python3
+
+import requests
+import json
+import os
+
+url = 'https://graphql.anilist.co'
+variables = {}
+home = os.path.expanduser("~")
+subdir = "notes/misc"
+
+
+# Here we define our query as a multi-line string
+query = '''
+query ($name: String, $type: MediaType) {
+MediaListCollection (userName: $name, type: $type) {
+ lists {
+ status
+ entries {
+ media {
+ title {
+ romaji
+ }
+ }
+ }
+ }
+ }
+}
+'''
+
+variables['name'] = input('Enter username: ')
+
+for MediaType in ['ANIME', 'MANGA']:
+
+ variables['type'] = MediaType
+
+ response = requests.post(url, json={'query': query, 'variables': variables})
+ data = response.json()
+
+ with open(os.path.join(home, subdir, "AniList_{}_{}.txt".format(variables['name'], variables['type'])), "w") as f:
+ for sublist in data["data"]["MediaListCollection"]["lists"]:
+ print('----- {} -----'.format(sublist["status"]), end="\n"*2, file=f)
+ for entry in sublist["entries"]:
+ print(entry["media"]["title"]["romaji"], file=f)
+ print("\n", file=f)
diff --git a/pyscripts/crx_getter.py b/pyscripts/crx_getter.py
new file mode 100755
index 0000000..412d2e2
--- /dev/null
+++ b/pyscripts/crx_getter.py
@@ -0,0 +1,141 @@
+#!/usr/bin/python3
+
+import requests
+import re
+import os
+import sys
+
+def treat_url(url):
+
+ check_url = re.search("^https:\\/\\/chromewebstore\\.google\\.com\\/detail\\/.*\\/[a-zA-Z0-9]{32}$", url)
+
+ if check_url:
+ print("URL")
+ ext_name = url.split('/')[4]
+ ext_id = url.split('/')[5]
+ yield ext_name, ext_id
+ else:
+ print("ERROR - INVALID INPUT")
+ sys.exit()
+
+def treat_input(arg):
+
+ check_file = re.search("^.*\\.txt$", arg)
+
+ if check_file and os.path.isfile(arg):
+ print("Text file")
+ with open(arg) as f:
+ for line in f:
+ print(line.rstrip())
+ url_generator = treat_url(line.rstrip())
+ for ext_name, ext_id in url_generator:
+ yield ext_name, ext_id
+ else:
+ print("Not text file")
+ url_generator = treat_url(arg)
+ for ext_name, ext_id in url_generator:
+ yield ext_name, ext_id
+
+
+def get_crx(ext_name, ext_id):
+
+ crx_filename = ext_name + '.crx'
+ crx_x = 'id=' + ext_id + '&installsource=ondemand&uc'
+ chromium_version = os.popen("chromium --version | awk '{print $2}'").read().rstrip('\n')
+
+ cookies = {
+ 'NID': '511=kbP9Akj2jywBWVd78MXD_ul3aupm6fd1zqmUJ-7H_eeY9FsyWAKsaoL4lvzkk7sot6jL6uabwxoLyFJPC5ZRWolSB8M980A3CP1DzmCu7oxIk6jP7yKThJIUhKmUJFrEHArN_Q8GFgqEwLTIHoK4vLHJo84n8JPoOxioxBq0r0I',
+ 'AEC': 'AakniGNT4RJzcCee9888Rkbuc7DjnzbyPxhy42p3TxZAqz957PuAUVPuzwQ',
+ '1P_JAR': '2023-01-06-17',
+ }
+
+ headers = {
+ 'authority': 'clients2.google.com',
+ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
+ 'accept-language': 'en-US,en;q=0.9',
+ # 'cookie': 'NID=511=kbP9Akj2jywBWVd78MXD_ul3aupm6fd1zqmUJ-7H_eeY9FsyWAKsaoL4lvzkk7sot6jL6uabwxoLyFJPC5ZRWolSB8M980A3CP1DzmCu7oxIk6jP7yKThJIUhKmUJFrEHArN_Q8GFgqEwLTIHoK4vLHJo84n8JPoOxioxBq0r0I; AEC=AakniGNT4RJzcCee9888Rkbuc7DjnzbyPxhy42p3TxZAqz957PuAUVPuzwQ; 1P_JAR=2023-01-06-17',
+ 'referer': 'https://crxextractor.com/',
+ 'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108"',
+ 'sec-ch-ua-mobile': '?0',
+ 'sec-ch-ua-platform': '"Linux"',
+ 'sec-fetch-dest': 'document',
+ 'sec-fetch-mode': 'navigate',
+ 'sec-fetch-site': 'cross-site',
+ 'sec-fetch-user': '?1',
+ 'upgrade-insecure-requests': '1',
+ 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
+ 'x-client-data': 'CJv6ygE=',
+ }
+
+ params = {
+ 'response': 'redirect',
+ 'prodversion': chromium_version,
+ 'acceptformat': 'crx3',
+ 'x': crx_x,
+ }
+
+ response = requests.get('https://clients2.google.com/service/update2/crx', params=params, cookies=cookies, headers=headers)
+
+ crx_url = response.url
+ response = requests.get(crx_url)
+
+ crx_file = open(crx_filename, 'wb')
+ crx_file.write(response.content)
+ crx_file.close()
+
+ return crx_filename
+
+
+def crx_to_zip(crx_filename):
+
+ i = 0
+ zip_filename = crx_filename[:-4] + '.zip'
+
+ with open(crx_filename, "rb") as f:
+ byte1 = f.read(1)
+ byte2 = f.read(1)
+ while ( byte2 and not (byte1 == b'P' and byte2 == b'K')):
+ byte1 = byte2
+ byte2 = f.read(1)
+ i += 1
+
+ with open(crx_filename, "rb") as in_file:
+ with open(zip_filename, "wb") as out_file:
+ out_file.write(in_file.read()[i:])
+
+ return zip_filename
+
+
+def main():
+
+ args = sys.argv
+
+ if 3 <= len(args) <= 4 and args.count('-i') == 1 and args.index('-i') < len(args) - 1:
+ pass
+ elif len(args) == 2 and args.count('-h') == 1:
+ print("Usage: python3 crxgetter.py -i file.txt/URL [-k to keep .crx file]")
+ sys.exit()
+ else:
+ print("ERROR - INVALID USAGE")
+ print("Usage: python3 crxgetter.py -i file.txt/URL [-k to keep .crx file]")
+ sys.exit()
+
+ crx_keep = True if args.count('-k') == 1 else False
+
+ extensions = dict(treat_input(args[args.index('-i') + 1]))
+ for key in extensions:
+ print(key, '->', extensions[key])
+ crx_filename = get_crx(key, extensions[key])
+ zip_filename = crx_to_zip(crx_filename)
+ print("Created " + zip_filename)
+ if crx_keep:
+ print("Preserved " + crx_filename)
+ else:
+ os.remove(crx_filename)
+
+
+if __name__ == "__main__":
+ main()
+
+# TO-DO
+# SIMPLE GETOPTS ARG PARSER
diff --git a/pyscripts/nyu_wide_rsync.py b/pyscripts/nyu_wide_rsync.py
new file mode 100755
index 0000000..54b6c39
--- /dev/null
+++ b/pyscripts/nyu_wide_rsync.py
@@ -0,0 +1,596 @@
+#!/usr/bin/python3
+
+import re, os, sys, subprocess, shlex
+import xml.etree.ElementTree as ET
+import argparse, unicodedata
+
+SITES_DEFAULT="~/.config/wide_rsync/transfer_sites.xml"
+SITE_TYPES=["transfer", "snapshot", "custom"]
+LOCATION_TYPES=["local", "external_drive", "ssh_server", "android_device"]
+QUIET_LVL=0
+WARNING_COLOR="\033[1;33m"
+ERROR_COLOR = "\033[1;31m"
+RESET_COLOR="\033[0m"
+
+class Site:
+ def __init__(self, name, source, destination, short_flags, long_flags, filters):
+ self.name = name
+ self.source = source
+ self.destination = destination
+ self.short_flags = short_flags
+ self.long_flags = long_flags
+ self.filters = filters
+
+ def __str__(self):
+ pass
+
+class SnapshotSite(Site):
+ def __init__(self, name, source, destination, short_flags, long_flags, filters, snap_base, snap_scheme, snap_rotation):
+ super().__init__(self, name, source, destination, short_flags, long_flags, filters)
+ self.snap_base = snap_base
+ self.snap_scheme = snap_scheme
+ self.snap_rotation = snap_rotation
+
+class Location:
+ def __init__(self, path):
+ self.path = path
+
+ def __str__(self):
+ pass
+
+class LocalLocation(Location):
+ def __init__(self, path):
+ super().__init__(self, path)
+
+class ExternalLocation(Location):
+ def __init__(self, path):
+ super().__init__(self, path)
+
+class SSHLocation(Location):
+ # ssh_username, ssh_port, ssh_privkey
+ def __init__(self, path):
+ super().__init__(self, path, username, port, privkey)
+ self.username = username
+ self.port = port
+ self.privkey = privkey
+
+class AndroidLocation(Location):
+ def __init__(self, path):
+ super().__init__(self, path)
+
+
+def query_yes_no(question, default="yes"):
+
+ valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
+ if default is None:
+ prompt = " [y/n] "
+ elif default == "yes":
+ prompt = " [Y/n] "
+ elif default == "no":
+ prompt = " [y/N] "
+ else:
+ raise ValueError("invalid default answer: '%s'" % default)
+
+ while True:
+ sys.stdout.write(question + prompt)
+ choice = input().lower()
+ if default is not None and choice == "":
+ return valid[default]
+ elif choice in valid:
+ return valid[choice]
+ else:
+ sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
+
+
+def check_children(node, expected_children):
+
+ count = 0
+ expected_count = len(node.findall("./*"))
+ for child in expected_children:
+ count = count + len(node.findall(f"./{child}"))
+
+ return ( count == expected_count )
+
+
+def validate_sites(sites):
+
+ global QUIET_LVL
+
+ errors = 0
+
+ # GO BACK TO THIS ONE
+ if sites is None:
+ print(f"{ERROR_COLOR}ERROR - Sites file is empty.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if len(sites.findall("./notification[@type='success']")) > 1 or len(sites.findall("./notification[@type='failure']")) > 1 or len(sites.findall("./notification")) > 2:
+ print(f"{ERROR_COLOR}ERROR - Too many notification scripts in sites file.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if len(sites.findall("./site")) < 1:
+ print(f"{ERROR_COLOR}ERROR - No sites configured in sites file.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if not check_children(sites, ['notification', 'site']):
+ print(f"{ERROR_COLOR}ERROR - Unknown elements found [NODE: sites].{RESET_COLOR}\n")
+ errors = errors + 1
+
+ ids = []
+ for site in sites.findall('site'):
+
+ if not site.get('id') or not site.get('name'):
+ print(f"{ERROR_COLOR}ERROR - Site {site} has no name or ID.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if site.get('id') in ids:
+ print(f"{ERROR_COLOR}ERROR - One or more sites with identical IDs exist.{RESET_COLOR}\n")
+ errors = errors + 1
+ else:
+ ids.append(site.get('id'))
+
+ if not check_children(site, ['source', 'destination', 'params', 'flags', 'filters']):
+ print(f"{ERROR_COLOR}ERROR - Unknown elements found [NODE: site {site.get('id')}].{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if len(site.findall("./source")) != 1 or len(site.findall("./destination")) != 1 :
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')} contains bad number of sources/destinations (must be exactly 1 of each){RESET_COLOR}.\n")
+ errors = errors + 1
+ elif not site.find("./source").text or not site.find("./destination").text:
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')} contains incomplete source/destination.{RESET_COLOR}\n")
+ errors = errors + 1
+ elif site.find("./source").get('type') is None or site.find("./destination").get('type') is None:
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')}'s source/destination does not have a type (see: [-t] for list of available types).{RESET_COLOR}\n")
+ errors = errors + 1
+ elif site.find("./source").get('type') not in SITE_TYPES or site.find("./destination").get('type') not in SITE_TYPES:
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')} contains unknown type of source/destination (see: [-t] for list of available types).{RESET_COLOR}\n")
+ errors = errors + 1
+
+ for entry in list(sites.iter()):
+ if entry.text is None:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - Empty entries exist in site file. May cause problems with program execution.{RESET_COLOR}\n")
+ break
+
+ return errors
+
+
+def get_sites_root(sites_location):
+ tree = ET.parse(sites_location)
+ sites = tree.getroot()
+ return sites
+
+
+def get_site_params(site):
+
+ site_params = {}
+ for param in site.find('params').findall('param'):
+ site_params[param.get('type')] = param.text
+
+ return site_params
+
+
+def get_site_flags(site):
+
+ global QUIET_LVL
+
+ arg = ""
+ site_flags = []
+
+ if len(list(site.iter('flags')) + list(site.iter('flag'))) == 0:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - No flags found for site {site.get('id')}. Skipping...{RESET_COLOR}\n")
+ return site_flags
+
+ for flag in site.find('flags').findall('flag'):
+ arg = ""
+ if not flag.text: continue
+ if flag.get('is_long') == "true":
+ arg = "--" + flag.text
+ else:
+ arg = "-" + flag.text
+ site_flags.append(arg)
+ arg = ""
+
+ return site_flags
+
+
+def get_site_filters(site):
+
+ global QUIET_LVL
+
+ arg = ""
+ site_filters = []
+
+ if len(list(site.iter('filters')) + list(site.iter('filter'))) == 0:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - No filters found for site {site.get('id')}. Skipping...{RESET_COLOR}\n")
+ return site_filters
+
+ for site_filter in site.find('filters').findall('filter'):
+ flag = ""
+ if (site_filter.get('type') == "include" or site_filter.get('type') == "exclude") and site_filter.text:
+ flag = "--" + site_filter.get('type')
+ site_filters.append(flag)
+ site_filters.append(site_filter.text)
+
+ return site_filters
+
+
+def site_exists(sites, site_id):
+ for site in sites.findall('site'):
+ if site.get('id') == site_id:
+ return True
+ return False
+
+
+def site_is_available(sites, site_id):
+
+ global QUIET_LVL
+
+ available = True
+ exists = False
+
+ for site in sites.findall('site'):
+ if site.get('id') == site_id:
+ exists = True
+ for location in [site.find('source'), site.find('destination')]:
+ match location.get('type'):
+
+ case "local":
+ available = available and (True if os.path.exists(location.text) and os.path.isdir(location.text) else False)
+
+ case "external_drive":
+ for i in range(len(location.text) + 1):
+ if os.path.ismount(location.text[:i]):
+ available = True
+ break
+ available = False
+ available = available and (True if os.path.exists(location.text) and os.path.isdir(location.text) else False)
+
+ case "ssh_server":
+
+ location_user = re.search(r"^.*?\@", location.text)
+ location_user = location_user.group(0)[:-1] if location_user != None else ""
+
+ location_domain = re.search(r"^.*\:", location.text)
+ if location_domain != None:
+ location_domain = re.sub(r"^.*\@", "" , location_domain.group(0)[:-1])
+ else:
+ if not QUIET_LVL > 1: print(f"ERROR: IP Address or Domain Name not detected for site {site.get('name')}.\n")
+ return False
+
+ location_directory = re.search(r"\:(\/|\~\/)?([a-zA-Z0-9_\- ]+\/?)+|\:\/|\:\~\/?" , location.text)
+ if location_directory != None:
+ location_directory = location_directory.group(0)[1:]
+ else:
+ if not QUIET_LVL > 1: print(f"ERROR: Remote location not detected for site {site.get('name')}.\n")
+ return False
+
+ # location_directory_full = (location_user + "@" if location_user != "" else "") + location_domain + ":" + location_directory
+ location_domain_full = (location_user + "@" if location_user != "" else "") + location_domain
+
+ params = get_site_params(site)
+
+ ssh_port = params['ssh_port']
+ ssh_key_location = params['ssh_key_location']
+
+ writeable = 1
+ if subprocess.run(["ping", "-c", "1", "-w", "5", f"{location_domain}"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT).returncode == 0:
+ writeable = subprocess.run(["ssh", "-p", f"{ssh_port}", "-i", f"{ssh_key_location}" , f"{location_domain_full}", f"[[ -d {location_directory} ]]"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
+ available = available and (True if (writeable != 1 and writeable.returncode == 0) else False)
+
+ case "android_device":
+
+ # everything here except os.path.exists() horrible and redundant, but I had to do it
+
+ uid = os.getuid()
+ mtp_location = re.sub(r"\/run\/user\/{}\/gvfs\/mtp\:host\=".format(uid), "mtp://", location.text)
+
+ try:
+ gio1 = subprocess.check_output(["gio", "info", f"{location.text}"], stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ gio1 = str(e.output)
+ try:
+ gio2 = subprocess.check_output(["gio", "info", f"{mtp_location}"], stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ gio2 = str(e.output)
+
+ available = available and (True if os.path.exists(location.text) and gio1 == gio2 else False)
+
+ case "snapshot":
+ available = False
+
+ case _:
+ if not QUIET_LVL > 1: print("ERROR: Location type doesn't exist\n")
+ return False
+ break
+
+ return ( available and exists )
+
+
+def get_sites(sites, source_type=[], destination_type=[], all_flag=False):
+
+ site_infos = []
+
+ for site in sites.findall('site'):
+ site_info = [site.get('id'), site.get('name'), site.find('source').get('type'), site.find('destination').get('type'), site.find('source').text, site.find('destination').text]
+ if (source_type is None or site.find('source').get('type') in source_type) and (destination_type is None or site.find('destination').get('type') in destination_type):
+ if all_flag:
+ site_infos.append(site_info)
+ elif site_is_available(sites, site.get('id')):
+ site_infos.append(site_info)
+
+ return site_infos
+
+
+def compile_rsync_command(sites, site_id, DRY_RUN=False):
+
+ global QUIET_LVL
+
+ command_subproc = [["rsync"]]
+ command_postproc = [[]]
+
+ try:
+ subprocess.run(["rsync", "-h"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
+ except OSError as e:
+ print("ERROR - Rsync not found.\n", e, "\n")
+ sys.exit(0)
+
+
+ if not site_is_available(sites, site_id):
+ if not QUIET_LVL > 1: print(f"{ERROR_COLOR}ERROR - Site {site_id} not available.{RESET_COLOR}\n")
+ return 1
+
+ # FIND SITE
+ for site in sites.findall('site'):
+ if site.get('id') == site_id:
+
+ site_params = get_site_params(site)
+ site_source = site.find('source').text
+ site_destination = site.find('destination').text
+
+ # CONSTRUCT SSH-SPECIFIC FLAG
+ if site.find('source').get('type') == "ssh_server" or site.find('destination').get('type') == "ssh_server":
+ if site_params["ssh_port"] != None and site_params["ssh_key_location"] != None:
+ e = "ssh -p " + site_params["ssh_port"] + " -i " + site_params["ssh_key_location"]
+ command_subproc[0].extend(["-e", e ])
+ else:
+ if not QUIET_LVL > 1: print(f"{ERROR_COLOR}ERROR - Unable to compile SSH command for site {site_id} - Missing arguments.{RESET_COLOR}\n")
+ return 1
+
+ # CONSTRUCT SNAPSHOT-SPECIFIC FLAG AND POST-PROCESSING
+ if site.find('destination').get('snapshot') == "true":
+ if "/" not in site_params['snap_base']:
+
+ snap_base = site_params['snap_base'] if site_params['snap_base'] else "default."
+ snap_extension = ""
+
+ if site_params['snap_extension'].lower() == "date":
+ snap_extension = subprocess.check_output(['date', '+%Y-%m-%d@%T'], encoding='UTF-8')
+ else:
+ temp_proc = subprocess.Popen(['find', site_destination, '-mindepth', '1', '-maxdepth', '1', '!', '-type', 'l', '-iname', f"{site_params['snap_base']}*"], stdout=subprocess.PIPE)
+ snap_extension = subprocess.check_output(['wc', '-l'], stdin=temp_proc.stdout, encoding='UTF-8')
+ temp_proc.wait()
+
+ snap_name = snap_base + snap_extension
+ snap_name = snap_name[:-1]
+ site_destination_last = site_destination + ( "/last" if site_destination[-1] != "/" else "last" )
+ command_subproc[0].extend(["--link-dest", site_destination_last ])
+ site_destination = site_destination + ( f"/{snap_name}" if site_destination[-1] != "/" else f"{snap_name}" )
+
+ command_postproc = [['rm', '-f', site_destination_last], ['ln', '-s', site_destination, site_destination_last]]
+
+ else:
+ if not QUIET_LVL > 1: print(f"{ERROR_COLOR}ERROR - Unable to compile snapshot command for site {site_id} - Trailing slash in base name.{RESET_COLOR}\n")
+ return 1
+
+
+ # HANDLE FLAGS
+ command_subproc[0].extend(get_site_flags(site))
+ if DRY_RUN == True: command_subproc[0].append("--dry-run")
+
+ # HANDLE FILTERS
+ command_subproc[0].extend(get_site_filters(site))
+
+
+ # HANDLE DESTINATION DIRECTORY
+ command_subproc[0].append(site_destination)
+
+ # HANDLE POST-PROCESSING
+ if DRY_RUN == False and command_postproc != [[]]: command_subproc.extend(command_postproc)
+
+ break
+
+ if command_subproc != ["rsync"]:
+ return command_subproc
+ else:
+ return 1
+
+def run_notification_script(sites, site_id_list, return_code):
+
+ global QUIET_LVL
+
+ script_type = "failure" if return_code > 0 else "success"
+ script_command = ""
+
+ for script in sites.findall('notification'):
+ if script.get('type') == script_type:
+ script_command = re.sub("%ID", f"{site_id_list}" , script.text)
+
+ if script_command:
+ subprocess.run(shlex.split(script_command))
+ else:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - No valid notification script found. Skipping...{RESET_COLOR}\n")
+
+
+def print_site_list(site_list, list_all):
+
+ univ_l= 5
+ ls = [0] * 6
+ kanjicount = []
+ title = ["SITE ID" , "SITE NAME", "TRANSFER TYPE", "TRANSFER SOURCE", "TRANSFER DESTINATION"]
+
+ for site in site_list:
+ for i in range(len(ls)):
+ ls[i] = ls[i] if ls[i] > len(site[i]) else len(site[i])
+ kanjicount.append([0] * 6)
+
+ for site in site_list:
+ for i in range(len(ls)):
+ for k in site[i]:
+ if unicodedata.east_asian_width(k) == "W":
+ kanjicount[site_list.index(site)][i] = kanjicount[site_list.index(site)][i] - 1
+
+
+ ls_title = [0] * 6
+ bar = ""
+ formatstr= ""
+ for i in range(len(ls_title)):
+ ls_title[i] = ls[i] + univ_l
+ ls_title = [ls_title[0], ls_title[1], ls_title[2]+ls_title[3], ls_title[4], ls_title[5]]
+ for i in range(len(ls_title)):
+ formatstr = formatstr + "{:<" + str(ls_title[i]) + "}"
+ bar = bar + ("-" * (ls_title[i] - univ_l)) + (" " * univ_l)
+
+ print("LISTING ALL", "CONFIGURED" if list_all else "AVAILABLE", "TRANSFER SITES\n")
+ print(formatstr.format(*title))
+ print(bar)
+ # BAR SLIGHTLY OFFSET, FIGURE OUT WHY
+
+ for site in site_list:
+ formatstr=""
+ ls_site = [0] * 6
+ for i in range(len(ls_site)):
+ ls_site[i] = ls[i] + kanjicount[site_list.index(site)][i] + univ_l
+ ls_site = [ls_site[0], ls_site[1], ls_site[2]+ls_site[3], ls_site[4], ls_site[5]]
+ for i in range(len(ls_site)):
+ ls_site[i] = ls_site[i] if ls_site[i] > len(title[i]) else ( len(title[i]) + univ_l )
+ for i in range(len(ls_site)):
+ formatstr = formatstr + "{:<" + str(ls_site[i]) + "}"
+ print(formatstr.format(site[0], site[1], f"{site[2]}->{site[3]}", site[4], site[5]))
+
+ print()
+
+def main():
+
+ # IT'S PARSIN' TIME
+ parser = argparse.ArgumentParser(description="Perform rsync operations using a pre-configured list of transfer sites")
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("-l", "--list", help="list all available transfer sites", action="store_true")
+ group.add_argument("-L", "--list-all", help="list all configured transfer sites (including unavailable ones)", action="store_true")
+ group.add_argument("-t", "--list-types", help="list all known transfer site types (e.g. 'local', 'ssh_server'...)", action="store_true")
+ parser.add_argument("--source", help="filter list of transfer sites by source type (used with [-l|-L], view available types with [-t])", action="extend", nargs="+", type=str)
+ parser.add_argument("--destination", help="filter list of transfer sites by destination type (used with [-l|-L], view available types with [-t])", action="extend", nargs="+", type=str)
+ group.add_argument("-s", "--sites", help="select transfer sites to process", action="extend", nargs="+", type=str)
+ parser.add_argument("-n", "--dry-run", help="turn all queued site transfers into dry runs (see: rsync(1) [-n|--dry-run])", action="store_true")
+ parser.add_argument("-q", "--quiet-level", help="set quietness level of site transfers ([] - print errors and warnings, [-q] - print errors only, [-qq...] - print critical errors only, default: [])", action="count", default=0)
+ parser.add_argument("-p", "--prompt-frequency", help="set prompt frequency of site transfers ([] - don't prompt, [-p] - prompt only once, [-pp...] - prompt once for each rsync command, default: [])", action="count", default=0)
+ parser.add_argument("--notify-each", help="run configured notification script once after each site transfer (default: runs once after all site transfers are done)", action="store_true")
+ parser.add_argument("-i", "--input-file", help=f"set custom file location to process transfer sites from (default: {SITES_DEFAULT})", action="store", nargs=1, type=str)
+
+ print()
+
+ if len(sys.argv) == 1:
+ parser.print_usage()
+ print()
+ sys.exit(1)
+
+ args = parser.parse_args()
+
+ # QUIET LEVEL AND PROMPT FREQUENCY
+ global QUIET_LVL
+ QUIET_LVL = args.quiet_level
+ prompt_frequency = args.prompt_frequency
+
+ # LIST TYPES
+ if args.list_types == True:
+ response = "Currently implemented types of transfer sites are: "
+ for site_type in SITE_TYPES:
+ response = response + site_type + ", "
+ print(response[:-2], "\n")
+ sys.exit(0)
+
+ # FIND INPUT FILE
+ if args.input_file != None and os.path.isfile(os.path.abspath(os.path.expanduser(args.input_file))):
+ if not QUIET_LVL > 1: print ("Using custom site location:", os.path.abspath(os.path.expanduser(args.input_file)), "\n")
+ sites = get_sites_root(os.path.abspath(os.path.expanduser(args.input_file)))
+ elif os.path.isfile(os.path.abspath(os.path.expanduser(SITES_DEFAULT))):
+ if not QUIET_LVL > 1: print ("Using default site location:", os.path.abspath(os.path.expanduser(SITES_DEFAULT)), "\n")
+ sites = get_sites_root(os.path.abspath(os.path.expanduser(SITES_DEFAULT)))
+ else:
+ print ("ERROR: No site locations found. Exiting...\n")
+ sys.exit(1)
+
+ # VALIDATE INPUT FILE
+ errors = validate_sites(sites)
+ if errors > 0:
+ print("Number of errors:", errors, "\nExiting...\n")
+ sys.exit(1)
+
+ # CONFIGURE FILTER
+ if (args.source != None or args.destination != None) and args.list != True and args.list_all != True:
+ print("ERROR: --source and --destination can only be used on a list (see: [-l|-L])\n")
+ sys.exit(1)
+
+ # LIST SITES
+ if args.list == True or args.list_all == True:
+ site_list = get_sites(sites, args.source, args.destination, args.list_all)
+ print_site_list(site_list, args.list_all)
+ sys.exit(0)
+
+ # WHERE THE MAGIC HAPPENS
+ elif args.sites != None:
+ site_id_list = ""
+ for site_id in args.sites:
+ site_id_list = site_id_list + site_id + ", "
+ if not site_exists(sites, site_id):
+ print("ERROR: One or more supplied sites do not exist. Exiting...\n")
+ sys.exit(1)
+ site_id_list = site_id_list[:-2]
+
+ commands_list = []
+ all_skipped = True
+ return_code = 0
+ final_return_code = 0
+
+ for site_id in args.sites:
+ commands_list.append(compile_rsync_command(sites, site_id, args.dry_run))
+
+ query_1 = "Do you wish to run the rsync commands for sites " + site_id_list + (" (WARNING: ERROR IN ONE OR MORE SITES DETECTED)?" if 1 in commands_list else "?")
+ if prompt_frequency != 1 or (prompt_frequency == 1 and query_yes_no(query_1)):
+ for commands in commands_list:
+ final_return_code = 0
+ curr_site_id = site_id_list.split(", ")[commands_list.index(commands)]
+ query_2 = "Do you wish to run the rsync command for site " + curr_site_id + (" (WARNING: ERROR IN SITE DETECTED)?" if commands == 1 else "?")
+ if prompt_frequency != 2 or (prompt_frequency == 2 and query_yes_no(query_2)):
+ all_skipped = False
+ for command in commands:
+ return_code = subprocess.run(command).returncode if command != 1 else command
+ final_return_code = final_return_code + return_code
+ if return_code != 0: break
+ if args.notify_each: run_notification_script(sites, curr_site_id, final_return_code)
+ print()
+ else:
+ if not QUIET_LVL > 1: print("Skipping command...\n")
+ else: print()
+ if not (args.notify_each or all_skipped): run_notification_script(sites, site_id_list, final_return_code)
+ else:
+ if not QUIET_LVL > 1: print("Skipping all commands...\n")
+ else: print()
+
+ # END OF MAIN
+
+if __name__ == '__main__':
+ try:
+ main()
+ except KeyboardInterrupt:
+ print("Keyboard interrupted received. Exiting...\n")
+ try:
+ sys.exit(0)
+ except SystemExit:
+ os._exit(0)
+
+# TO-DO
+# REVERSE FLAG
+# FLAG FOR SHOWING COMMANDS TO RUN DURING PROMPT, ALSO FLAG FOR NOT RUNNING NOTIFICATION SCRIPT
+# FINISH IMPLEMENTING snap_count
+# FIX FORMATTING, DISPLAY x->y(snapshot) TYPE FOR SNAPSHOTS
+# THINK OF OTHER WAYS TO STAMP SNAPS
+# ONE DAY, CLEAN UP COMPILE_RSYNC FUNCTION INTO SUBROUTINES AND PROPERLY USE AN ARRAY FROM START TO FINISH
diff --git a/pyscripts/pixiv_auth.py b/pyscripts/pixiv_auth.py
new file mode 100755
index 0000000..f7dc5d1
--- /dev/null
+++ b/pyscripts/pixiv_auth.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+from argparse import ArgumentParser
+from base64 import urlsafe_b64encode
+from hashlib import sha256
+from pprint import pprint
+from secrets import token_urlsafe
+from sys import exit
+from urllib.parse import urlencode
+from webbrowser import open as open_url
+
+import requests
+
+# Latest app version can be found using GET /v1/application-info/android
+USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)"
+REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback"
+LOGIN_URL = "https://app-api.pixiv.net/web/v1/login"
+AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token"
+CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT"
+CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"
+
+
+def s256(data):
+ """S256 transformation method."""
+
+ return urlsafe_b64encode(sha256(data).digest()).rstrip(b"=").decode("ascii")
+
+
+def oauth_pkce(transform):
+ """Proof Key for Code Exchange by OAuth Public Clients (RFC7636)."""
+
+ code_verifier = token_urlsafe(32)
+ code_challenge = transform(code_verifier.encode("ascii"))
+
+ return code_verifier, code_challenge
+
+
+def print_auth_token_response(response):
+ data = response.json()
+
+ try:
+ access_token = data["access_token"]
+ refresh_token = data["refresh_token"]
+ except KeyError:
+ print("error:")
+ pprint(data)
+ exit(1)
+
+ print("access_token:", access_token)
+ print("refresh_token:", refresh_token)
+ print("expires_in:", data.get("expires_in", 0))
+
+
+def login():
+ code_verifier, code_challenge = oauth_pkce(s256)
+ login_params = {
+ "code_challenge": code_challenge,
+ "code_challenge_method": "S256",
+ "client": "pixiv-android",
+ }
+
+ open_url(f"{LOGIN_URL}?{urlencode(login_params)}")
+
+ try:
+ code = input("code: ").strip()
+ except (EOFError, KeyboardInterrupt):
+ return
+
+ response = requests.post(
+ AUTH_TOKEN_URL,
+ data={
+ "client_id": CLIENT_ID,
+ "client_secret": CLIENT_SECRET,
+ "code": code,
+ "code_verifier": code_verifier,
+ "grant_type": "authorization_code",
+ "include_policy": "true",
+ "redirect_uri": REDIRECT_URI,
+ },
+ headers={"User-Agent": USER_AGENT},
+ )
+
+ print_auth_token_response(response)
+
+
+def refresh(refresh_token):
+ response = requests.post(
+ AUTH_TOKEN_URL,
+ data={
+ "client_id": CLIENT_ID,
+ "client_secret": CLIENT_SECRET,
+ "grant_type": "refresh_token",
+ "include_policy": "true",
+ "refresh_token": refresh_token,
+ },
+ headers={"User-Agent": USER_AGENT},
+ )
+ print_auth_token_response(response)
+
+
+def main():
+ parser = ArgumentParser()
+ subparsers = parser.add_subparsers()
+ parser.set_defaults(func=lambda _: parser.print_usage())
+ login_parser = subparsers.add_parser("login")
+ login_parser.set_defaults(func=lambda _: login())
+ refresh_parser = subparsers.add_parser("refresh")
+ refresh_parser.add_argument("refresh_token")
+ refresh_parser.set_defaults(func=lambda ns: refresh(ns.refresh_token))
+ args = parser.parse_args()
+ args.func(args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyscripts/wide_rsync.py b/pyscripts/wide_rsync.py
new file mode 100755
index 0000000..dc0118f
--- /dev/null
+++ b/pyscripts/wide_rsync.py
@@ -0,0 +1,563 @@
+#!/usr/bin/python3
+
+import re, os, sys, subprocess, shlex
+import xml.etree.ElementTree as ET
+import argparse, unicodedata
+
+SITES_DEFAULT="~/.config/wide_rsync/transfer_sites.xml"
+SITE_TYPES=["local", "external_drive", "remote_server", "android_device", "snapshot", "custom"]
+QUIET_LVL=0
+WARNING_COLOR="\033[1;33m"
+ERROR_COLOR = "\033[1;31m"
+RESET_COLOR="\033[0m"
+
+
+def query_yes_no(question, default="yes"):
+
+ valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
+ if default is None:
+ prompt = " [y/n] "
+ elif default == "yes":
+ prompt = " [Y/n] "
+ elif default == "no":
+ prompt = " [y/N] "
+ else:
+ raise ValueError("invalid default answer: '%s'" % default)
+
+ while True:
+ sys.stdout.write(question + prompt)
+ choice = input().lower()
+ if default is not None and choice == "":
+ return valid[default]
+ elif choice in valid:
+ return valid[choice]
+ else:
+ sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
+
+
+def check_children(node, expected_children):
+
+ count = 0
+ expected_count = len(node.findall("./*"))
+ for child in expected_children:
+ count = count + len(node.findall(f"./{child}"))
+
+ return ( count == expected_count )
+
+
+def validate_sites(sites):
+
+ global QUIET_LVL
+
+ errors = 0
+
+ # GO BACK TO THIS ONE
+ if sites is None:
+ print(f"{ERROR_COLOR}ERROR - Sites file is empty.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if len(sites.findall("./notification[@type='success']")) > 1 or len(sites.findall("./notification[@type='failure']")) > 1 or len(sites.findall("./notification")) > 2:
+ print(f"{ERROR_COLOR}ERROR - Too many notification scripts in sites file.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if len(sites.findall("./site")) < 1:
+ print(f"{ERROR_COLOR}ERROR - No sites configured in sites file.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if not check_children(sites, ['notification', 'site']):
+ print(f"{ERROR_COLOR}ERROR - Unknown elements found [NODE: sites].{RESET_COLOR}\n")
+ errors = errors + 1
+
+ ids = []
+ for site in sites.findall('site'):
+
+ if not site.get('id') or not site.get('name'):
+ print(f"{ERROR_COLOR}ERROR - Site {site} has no name or ID.{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if site.get('id') in ids:
+ print(f"{ERROR_COLOR}ERROR - One or more sites with identical IDs exist.{RESET_COLOR}\n")
+ errors = errors + 1
+ else:
+ ids.append(site.get('id'))
+
+ if not check_children(site, ['source', 'destination', 'params', 'flags', 'filters']):
+ print(f"{ERROR_COLOR}ERROR - Unknown elements found [NODE: site {site.get('id')}].{RESET_COLOR}\n")
+ errors = errors + 1
+
+ if len(site.findall("./source")) != 1 or len(site.findall("./destination")) != 1 :
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')} contains bad number of sources/destinations (must be exactly 1 of each){RESET_COLOR}.\n")
+ errors = errors + 1
+ elif not site.find("./source").text or not site.find("./destination").text:
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')} contains incomplete source/destination.{RESET_COLOR}\n")
+ errors = errors + 1
+ elif site.find("./source").get('type') is None or site.find("./destination").get('type') is None:
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')}'s source/destination does not have a type (see: [-t] for list of available types).{RESET_COLOR}\n")
+ errors = errors + 1
+ elif site.find("./source").get('type') not in SITE_TYPES or site.find("./destination").get('type') not in SITE_TYPES:
+ print(f"{ERROR_COLOR}ERROR - Site {site.get('id')} contains unknown type of source/destination (see: [-t] for list of available types).{RESET_COLOR}\n")
+ errors = errors + 1
+
+ for entry in list(sites.iter()):
+ if entry.text is None:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - Empty entries exist in site file. May cause problems with program execution.{RESET_COLOR}\n")
+ break
+
+ return errors
+
+
+def get_sites_root(sites_location):
+ tree = ET.parse(sites_location)
+ sites = tree.getroot()
+ return sites
+
+
+def get_site_params(site):
+
+ site_params = {}
+ for param in site.find('params').findall('param'):
+ site_params[param.get('type')] = param.text
+
+ return site_params
+
+
+def get_site_flags(site):
+
+ global QUIET_LVL
+
+ arg = ""
+ site_flags = []
+
+ if len(list(site.iter('flags')) + list(site.iter('flag'))) == 0:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - No flags found for site {site.get('id')}. Skipping...{RESET_COLOR}\n")
+ return site_flags
+
+ for flag in site.find('flags').findall('flag'):
+ arg = ""
+ if not flag.text: continue
+ if flag.get('is_long') == "true":
+ arg = "--" + flag.text
+ else:
+ arg = "-" + flag.text
+ site_flags.append(arg)
+ arg = ""
+
+ return site_flags
+
+
+def get_site_filters(site):
+
+ global QUIET_LVL
+
+ arg = ""
+ site_filters = []
+
+ if len(list(site.iter('filters')) + list(site.iter('filter'))) == 0:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - No filters found for site {site.get('id')}. Skipping...{RESET_COLOR}\n")
+ return site_filters
+
+ for site_filter in site.find('filters').findall('filter'):
+ flag = ""
+ if (site_filter.get('type') == "include" or site_filter.get('type') == "exclude") and site_filter.text:
+ flag = "--" + site_filter.get('type')
+ site_filters.append(flag)
+ site_filters.append(site_filter.text)
+
+ return site_filters
+
+
+def site_exists(sites, site_id):
+ for site in sites.findall('site'):
+ if site.get('id') == site_id:
+ return True
+ return False
+
+
+def site_is_available(sites, site_id):
+
+ global QUIET_LVL
+
+ available = True
+ exists = False
+
+ for site in sites.findall('site'):
+ if site.get('id') == site_id:
+ exists = True
+ for location in [site.find('source'), site.find('destination')]:
+ match location.get('type'):
+
+ case "local":
+ location_full=os.path.abspath(os.path.expanduser(location.text))
+ available = available and (True if os.path.exists(location_full) and os.path.isdir(location_full) else False)
+
+ case "external_drive":
+ location_full=os.path.abspath(os.path.expanduser(location.text))
+ for i in range(len(location_full) + 1):
+ if os.path.ismount(location_full[:i]):
+ available = True
+ break
+ available = False
+ available = available and (True if os.path.exists(location_full) and os.path.isdir(location_full) else False)
+
+ case "remote_server":
+
+ location_user = re.search(r"^.*?\@", location.text)
+ location_user = location_user.group(0)[:-1] if location_user != None else ""
+
+ location_domain = re.search(r"^.*\:", location.text)
+ if location_domain != None:
+ location_domain = re.sub(r"^.*\@", "" , location_domain.group(0)[:-1])
+ else:
+ if not QUIET_LVL > 1: print(f"ERROR: IP Address or Domain Name not detected for site {site.get('name')}.\n")
+ return False
+
+ location_directory = re.search(r"\:(\/|\~\/)?([a-zA-Z0-9_\- ]+\/?)+|\:\/|\:\~\/?" , location.text)
+ if location_directory != None:
+ location_directory = location_directory.group(0)[1:]
+ else:
+ if not QUIET_LVL > 1: print(f"ERROR: Remote location not detected for site {site.get('name')}.\n")
+ return False
+
+ # location_directory_full = (location_user + "@" if location_user != "" else "") + location_domain + ":" + location_directory
+ location_domain_full = (location_user + "@" if location_user != "" else "") + location_domain
+
+ params = get_site_params(site)
+
+ ssh_port = params['ssh_port']
+ ssh_key_location = params['ssh_key_location']
+
+ writeable = 1
+ if subprocess.run(["ping", "-c", "1", "-w", "5", f"{location_domain}"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT).returncode == 0:
+ writeable = subprocess.run(["ssh", "-p", f"{ssh_port}", "-i", f"{ssh_key_location}" , f"{location_domain_full}", f"[[ -d {location_directory} ]]"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
+ available = available and (True if (writeable != 1 and writeable.returncode == 0) else False)
+
+ case "android_device":
+
+ # everything here except os.path.exists() horrible and redundant, but I had to do it
+
+ uid = os.getuid()
+ mtp_location = re.sub(r"\/run\/user\/{}\/gvfs\/mtp\:host\=".format(uid), "mtp://", location.text)
+
+ try:
+ gio1 = subprocess.check_output(["gio", "info", f"{location.text}"], stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ gio1 = str(e.output)
+ try:
+ gio2 = subprocess.check_output(["gio", "info", f"{mtp_location}"], stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ gio2 = str(e.output)
+
+ available = available and (True if os.path.exists(location.text) and gio1 == gio2 else False)
+
+ case "snapshot":
+ available = False
+
+ case _:
+ if not QUIET_LVL > 1: print("ERROR: Location type doesn't exist\n")
+ return False
+ break
+
+ return ( available and exists )
+
+
+def get_sites(sites, source_type=[], destination_type=[], all_flag=False):
+
+ site_infos = []
+
+ for site in sites.findall('site'):
+ site_info = [site.get('id'), site.get('name'), site.find('source').get('type'), site.find('destination').get('type'), site.find('source').text, site.find('destination').text]
+ if (source_type is None or site.find('source').get('type') in source_type) and (destination_type is None or site.find('destination').get('type') in destination_type):
+ if all_flag:
+ site_infos.append(site_info)
+ elif site_is_available(sites, site.get('id')):
+ site_infos.append(site_info)
+
+ return site_infos
+
+
+def compile_rsync_command(sites, site_id, DRY_RUN=False):
+
+ global QUIET_LVL
+
+ command_subproc = [["rsync"]]
+ command_postproc = [[]]
+
+ try:
+ subprocess.run(["rsync", "-h"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
+ except OSError as e:
+ print("ERROR - Rsync not found.\n", e, "\n")
+ sys.exit(0)
+
+
+ if not site_is_available(sites, site_id):
+ if not QUIET_LVL > 1: print(f"{ERROR_COLOR}ERROR - Site {site_id} not available.{RESET_COLOR}\n")
+ return 1
+
+ # FIND SITE
+ for site in sites.findall('site'):
+ if site.get('id') == site_id:
+
+ site_params = get_site_params(site)
+ site_source = site.find('source').text
+ site_destination = site.find('destination').text
+
+ if site.find('source').get('type') == "local":
+ site_source = os.path.abspath(os.path.expanduser(site_source))
+ if site.find('destination').get('type') == "local":
+ site_destination = os.path.abspath(os.path.expanduser(site_destination))
+
+ # CONSTRUCT SSH-SPECIFIC FLAG
+ if site.find('source').get('type') == "remote_server" or site.find('destination').get('type') == "remote_server":
+ if site_params["ssh_port"] != None and site_params["ssh_key_location"] != None:
+ e = "ssh -p " + site_params["ssh_port"] + " -i " + os.path.abspath(os.path.expanduser(site_params["ssh_key_location"]))
+ command_subproc[0].extend(["-e", e ])
+ else:
+ if not QUIET_LVL > 1: print(f"{ERROR_COLOR}ERROR - Unable to compile SSH command for site {site_id} - Missing arguments.{RESET_COLOR}\n")
+ return 1
+
+ # CONSTRUCT SNAPSHOT-SPECIFIC FLAG AND POST-PROCESSING
+ if site.find('destination').get('snapshot') == "true":
+ if "/" not in site_params['snap_base']:
+
+ snap_base = site_params['snap_base'] if site_params['snap_base'] else "default."
+ snap_extension = ""
+
+ if site_params['snap_extension'].lower() == "date":
+ snap_extension = subprocess.check_output(['date', '+%Y-%m-%d@%T'], encoding='UTF-8')
+ else:
+ temp_proc = subprocess.Popen(['find', site_destination, '-mindepth', '1', '-maxdepth', '1', '!', '-type', 'l', '-iname', f"{site_params['snap_base']}*"], stdout=subprocess.PIPE)
+ snap_extension = subprocess.check_output(['wc', '-l'], stdin=temp_proc.stdout, encoding='UTF-8')
+ temp_proc.wait()
+
+ snap_name = snap_base + snap_extension
+ snap_name = snap_name[:-1]
+ site_destination_last = site_destination + ( "/last" if site_destination[-1] != "/" else "last" )
+ command_subproc[0].extend(["--link-dest", site_destination_last ])
+ site_destination = site_destination + ( f"/{snap_name}" if site_destination[-1] != "/" else f"{snap_name}" )
+
+ command_postproc = [['rm', '-f', site_destination_last], ['ln', '-s', site_destination, site_destination_last]]
+
+ else:
+ if not QUIET_LVL > 1: print(f"{ERROR_COLOR}ERROR - Unable to compile snapshot command for site {site_id} - Trailing slash in base name.{RESET_COLOR}\n")
+ return 1
+
+
+ # HANDLE FLAGS
+ command_subproc[0].extend(get_site_flags(site))
+ if DRY_RUN == True: command_subproc[0].append("--dry-run")
+
+ # HANDLE FILTERS
+ command_subproc[0].extend(get_site_filters(site))
+
+ # HANDLE SOURCE DIRECTORY
+ arg=""
+ if site.find('source').get('preserve_dir') == "true":
+ arg = (site_source if site_source[-1] != "/" else site_source[:-1])
+ else:
+ arg = (site_source if site_source[-1] == "/" else site_source + "/")
+ command_subproc[0].append(arg)
+
+ # HANDLE DESTINATION DIRECTORY
+ command_subproc[0].append(site_destination)
+
+ # HANDLE POST-PROCESSING
+ if DRY_RUN == False and command_postproc != [[]]: command_subproc.extend(command_postproc)
+
+ break
+
+ if command_subproc != ["rsync"]:
+ return command_subproc
+ else:
+ return 1
+
+def run_notification_script(sites, site_id_list, return_code):
+
+ global QUIET_LVL
+
+ script_type = "failure" if return_code > 0 else "success"
+ script_command = ""
+
+ for script in sites.findall('notification'):
+ if script.get('type') == script_type:
+ script_command = re.sub("%ID", f"{site_id_list}" , script.text)
+ script_command = re.sub("~", os.getenv('HOME') , script_command)
+
+ if script_command:
+ subprocess.run(shlex.split(script_command))
+ else:
+ if not QUIET_LVL > 0: print(f"{WARNING_COLOR}WARNING - No valid notification script found. Skipping...{RESET_COLOR}\n")
+
+
+def print_site_list(site_list, list_all):
+
+ univ_l= 5
+ ls = [0] * 6
+ kanjicount = []
+ title = ["SITE ID" , "SITE NAME", "TRANSFER TYPE", "TRANSFER SOURCE", "TRANSFER DESTINATION"]
+
+ for site in site_list:
+ for i in range(len(ls)):
+ ls[i] = ls[i] if ls[i] > len(site[i]) else len(site[i])
+ kanjicount.append([0] * 6)
+
+ for site in site_list:
+ for i in range(len(ls)):
+ for k in site[i]:
+ if unicodedata.east_asian_width(k) == "W":
+ kanjicount[site_list.index(site)][i] = kanjicount[site_list.index(site)][i] - 1
+
+
+ ls_title = [0] * 6
+ bar = ""
+ formatstr= ""
+ for i in range(len(ls_title)):
+ ls_title[i] = ls[i] + univ_l
+ ls_title = [ls_title[0], ls_title[1], ls_title[2]+ls_title[3], ls_title[4], ls_title[5]]
+ for i in range(len(ls_title)):
+ formatstr = formatstr + "{:<" + str(ls_title[i]) + "}"
+ bar = bar + ("-" * (ls_title[i] - univ_l)) + (" " * univ_l)
+
+ print("LISTING ALL", "CONFIGURED" if list_all else "AVAILABLE", "TRANSFER SITES\n")
+ print(formatstr.format(*title))
+ print(bar)
+ # BAR SLIGHTLY OFFSET, FIGURE OUT WHY
+
+ for site in site_list:
+ formatstr=""
+ ls_site = [0] * 6
+ for i in range(len(ls_site)):
+ ls_site[i] = ls[i] + kanjicount[site_list.index(site)][i] + univ_l
+ ls_site = [ls_site[0], ls_site[1], ls_site[2]+ls_site[3], ls_site[4], ls_site[5]]
+ for i in range(len(ls_site)):
+ ls_site[i] = ls_site[i] if ls_site[i] > len(title[i]) else ( len(title[i]) + univ_l )
+ for i in range(len(ls_site)):
+ formatstr = formatstr + "{:<" + str(ls_site[i]) + "}"
+ print(formatstr.format(site[0], site[1], f"{site[2]}->{site[3]}", site[4], site[5]))
+
+ print()
+
+def main():
+
+ # IT'S PARSIN' TIME
+ parser = argparse.ArgumentParser(description="Perform rsync operations using a pre-configured list of transfer sites")
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("-l", "--list", help="list all available transfer sites", action="store_true")
+ group.add_argument("-L", "--list-all", help="list all configured transfer sites (including unavailable ones)", action="store_true")
+ group.add_argument("-t", "--list-types", help="list all known transfer site types (e.g. 'local', 'remote_server'...)", action="store_true")
+ parser.add_argument("--source", help="filter list of transfer sites by source type (used with [-l|-L], view available types with [-t])", action="extend", nargs="+", type=str)
+ parser.add_argument("--destination", help="filter list of transfer sites by destination type (used with [-l|-L], view available types with [-t])", action="extend", nargs="+", type=str)
+ group.add_argument("-s", "--sites", help="select transfer sites to process", action="extend", nargs="+", type=str)
+ parser.add_argument("-n", "--dry-run", help="turn all queued site transfers into dry runs (see: rsync(1) [-n|--dry-run])", action="store_true")
+ parser.add_argument("-q", "--quiet-level", help="set quietness level of site transfers ([] - print errors and warnings, [-q] - print errors only, [-qq...] - print critical errors only, default: [])", action="count", default=0)
+ parser.add_argument("-p", "--prompt-frequency", help="set prompt frequency of site transfers ([] - don't prompt, [-p] - prompt only once, [-pp...] - prompt once for each rsync command, default: [])", action="count", default=0)
+ parser.add_argument("--notify-each", help="run configured notification script once after each site transfer (default: runs once after all site transfers are done)", action="store_true")
+ parser.add_argument("-i", "--input-file", help=f"set custom file location to process transfer sites from (default: {SITES_DEFAULT})", action="store", nargs=1, type=str)
+
+ print()
+
+ if len(sys.argv) == 1:
+ parser.print_usage()
+ print()
+ sys.exit(1)
+
+ args = parser.parse_args()
+
+ # QUIET LEVEL AND PROMPT FREQUENCY
+ global QUIET_LVL
+ QUIET_LVL = args.quiet_level
+ prompt_frequency = args.prompt_frequency
+
+ # LIST TYPES
+ if args.list_types == True:
+ response = "Currently implemented types of transfer sites are: "
+ for site_type in SITE_TYPES:
+ response = response + site_type + ", "
+ print(response[:-2], "\n")
+ sys.exit(0)
+
+ # FIND INPUT FILE
+ if args.input_file != None and os.path.isfile(os.path.abspath(os.path.expanduser(args.input_file))):
+ if not QUIET_LVL > 1: print ("Using custom site location:", os.path.abspath(os.path.expanduser(args.input_file)), "\n")
+ sites = get_sites_root(os.path.abspath(os.path.expanduser(args.input_file)))
+ elif os.path.isfile(os.path.abspath(os.path.expanduser(SITES_DEFAULT))):
+ if not QUIET_LVL > 1: print ("Using default site location:", os.path.abspath(os.path.expanduser(SITES_DEFAULT)), "\n")
+ sites = get_sites_root(os.path.abspath(os.path.expanduser(SITES_DEFAULT)))
+ else:
+ print ("ERROR: No site locations found. Exiting...\n")
+ sys.exit(1)
+
+ # VALIDATE INPUT FILE
+ errors = validate_sites(sites)
+ if errors > 0:
+ print("Number of errors:", errors, "\nExiting...\n")
+ sys.exit(1)
+
+ # CONFIGURE FILTER
+ if (args.source != None or args.destination != None) and args.list != True and args.list_all != True:
+ print("ERROR: --source and --destination can only be used on a list (see: [-l|-L])\n")
+ sys.exit(1)
+
+ # LIST SITES
+ if args.list == True or args.list_all == True:
+ site_list = get_sites(sites, args.source, args.destination, args.list_all)
+ print_site_list(site_list, args.list_all)
+ sys.exit(0)
+
+ # WHERE THE MAGIC HAPPENS
+ elif args.sites != None:
+ site_id_list = ""
+ for site_id in args.sites:
+ site_id_list = site_id_list + site_id + ", "
+ if not site_exists(sites, site_id):
+ print("ERROR: One or more supplied sites do not exist. Exiting...\n")
+ sys.exit(1)
+ site_id_list = site_id_list[:-2]
+
+ commands_list = []
+ all_skipped = True
+ return_code = 0
+ final_return_code = 0
+
+ for site_id in args.sites:
+ commands_list.append(compile_rsync_command(sites, site_id, args.dry_run))
+
+ query_1 = "Do you wish to run the rsync commands for sites " + site_id_list + (" (WARNING: ERROR IN ONE OR MORE SITES DETECTED)?" if 1 in commands_list else "?")
+ if prompt_frequency != 1 or (prompt_frequency == 1 and query_yes_no(query_1)):
+ for commands in commands_list:
+ final_return_code = 0
+ curr_site_id = site_id_list.split(", ")[commands_list.index(commands)]
+ query_2 = "Do you wish to run the rsync command for site " + curr_site_id + (" (WARNING: ERROR IN SITE DETECTED)?" if commands == 1 else "?")
+ if prompt_frequency != 2 or (prompt_frequency == 2 and query_yes_no(query_2)):
+ all_skipped = False
+ for command in commands:
+ return_code = subprocess.run(command).returncode if command != 1 else command
+ final_return_code = final_return_code + return_code
+ if return_code != 0: break
+ if args.notify_each: run_notification_script(sites, curr_site_id, final_return_code)
+ print()
+ else:
+ if not QUIET_LVL > 1: print("Skipping command...\n")
+ else: print()
+ if not (args.notify_each or all_skipped): run_notification_script(sites, site_id_list, final_return_code)
+ else:
+ if not QUIET_LVL > 1: print("Skipping all commands...\n")
+ else: print()
+
+ # END OF MAIN
+
+if __name__ == '__main__':
+ try:
+ main()
+ except KeyboardInterrupt:
+ print("Keyboard interrupted received. Exiting...\n")
+ try:
+ sys.exit(0)
+ except SystemExit:
+ os._exit(0)
+
+# TO-DO
+# FLAG FOR SHOWING COMMANDS TO RUN DURING PROMPT, ALSO FLAG FOR NOT RUNNING NOTIFICATION SCRIPT
+# FINISH IMPLEMENTING snap_count
+# FIX FORMATTING, DISPLAY x->y(snapshot) TYPE FOR SNAPSHOTS
+# THINK OF OTHER WAYS TO STAMP SNAPS
+# ONE DAY, CLEAN UP COMPILE_RSYNC FUNCTION INTO SUBROUTINES AND PROPERLY USE AN ARRAY FROM START TO FINISH
diff --git a/scriptlets/adbor.sh b/scriptlets/adbor.sh
new file mode 100755
index 0000000..ed060f8
--- /dev/null
+++ b/scriptlets/adbor.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# if not this, keep prompting in loop
+# adb devices | grep device$
+
+MODE="$1"
+
+[[ ! $( echo "$MODE" | grep -e "^add$" -e "^remove$" ) ]] && echo -e "usage: $0 add/remove" && exit -3
+
+while ! adb devices | grep -iq "device$" ; do
+
+ echo "No authorized devices found."
+ for i in {1..5}; do
+ echo "Recheck in ${i}..."
+ sleep 1
+ done
+done
+
+DEVICE="$( adb devices | grep -i device$ | awk '{print $1}' | fzf -1 --prompt "Device: " )"
+[[ -z "$DEVICE" ]] && echo "No device selected. Exiting." && exit 0
+
+if echo "$MODE" | grep -iqF "remove" ; then
+
+ PACKAGE=$( adb -s "${DEVICE}" shell "pm list packages" | sed -r "s|^package:||" | fzf --prompt "[Device: ${DEVICE}] Package to remove: " )
+ [[ -z "$PACKAGE" ]] && echo "No package selected. Exiting." && exit 0
+
+ read -p "Are you sure you want to uninstall package \"${PACKAGE}\"? [Y/n] " -n 1
+ # if [[ $REPLY =~ ^[Yy]$ ]]
+ if [[ $REPLY =~ ^[Nn]$ ]]; then
+ echo "Uninstall aborted." && exit -1
+ fi
+ adb -s "${DEVICE}" shell "pm uninstall -k --user 0 ${PACKAGE}" && echo "Package uninstalled." && exit 1
+
+elif echo "$MODE" | grep -iqF "add" ; then
+
+ PACKAGE=$( comm -13 <(adb -s "${DEVICE}" shell "pm list packages" | sort) <(adb -s "${DEVICE}" shell "pm list packages -u" | sort) | sed -r "s|^package:||" | fzf --prompt "[Device: ${DEVICE}] Package to add: " )
+ [[ -z "$PACKAGE" ]] && echo "No package selected. Exiting." && exit 1
+
+ read -p "Are you sure you want to reinstall package \"${PACKAGE}\"? [Y/n] " -n 1
+ # if [[ $REPLY =~ ^[Yy]$ ]]
+ if [[ $REPLY =~ ^[Nn]$ ]]; then
+ echo "Reinstall aborted." && exit -2
+ fi
+
+ adb -s "${DEVICE}" shell "pm install-existing ${PACKAGE}" && echo "Package reinstalled." && exit 2
+
+else
+ echo -e "usage: $0 add/remove" && exit -3
+fi
diff --git a/scriptlets/androidmount.sh b/scriptlets/androidmount.sh
new file mode 100755
index 0000000..0686b4a
--- /dev/null
+++ b/scriptlets/androidmount.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+MOUNTDIR="$HOME/1Misc/mnt"
+[[ ! -d "$MOUNTDIR" ]] && mkdir -p "$MOUNTDIR"
+[[ -z "$(jmtpfs -l)" ]] && exit
+[[ "$1" == "-u" && -n "$( grep "jmtpfs $MOUNTDIR" "/etc/mtab" )" ]] && fusermount -u "$MOUNTDIR"
+[[ -z "$1" && -z "$( grep "jmtpfs $MOUNTDIR" "/etc/mtab" )" ]] && jmtpfs "$MOUNTDIR"
+
diff --git a/scriptlets/backup_rotation.sh b/scriptlets/backup_rotation.sh
new file mode 100755
index 0000000..891933a
--- /dev/null
+++ b/scriptlets/backup_rotation.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+BACKUP_ROOT="/home/jay/Downloads/Snapshots"
+BACKUP_REGEX='.*[0-9]+-[0-9]+-[0-9]+_[0-9]+:[0-9]+:[0-9]+'
+RETAIN_NUM=30
+
+[[ ! -d "$BACKUP_ROOT" ]] && echo "[$(date '+%Y-%m-%d_%H:%M:%S')] ERROR. Root backup directory '$BACKUP_ROOT' not found. Exiting..." && exit -1
+
+BACKUP_SITES=()
+while IFS= read -r -d $'\0'; do
+ BACKUP_SITES+=("$REPLY")
+done < <(find "$BACKUP_ROOT" -maxdepth 1 -mindepth 1 -type d -print0)
+
+[[ ${#BACKUP_SITES[@]} -eq 0 ]] && echo "[$(date '+%Y-%m-%d_%H:%M:%S')] Error. No backup sites found in root directory '$BACKUP_ROOT'. Exiting..." && exit -1
+
+for SITE in ${BACKUP_SITES[@]}; do
+
+ TOTAL_NUM=$(ls -1 "$SITE" | grep -cE "$BACKUP_REGEX")
+ DELETE_NUM=$((TOTAL_NUM - RETAIN_NUM))
+
+ if [ $DELETE_NUM -gt 0 ]; then
+
+ old_backups=$(ls -1 "$SITE" | grep -E "$BACKUP_REGEX" | sort | head -n $DELETE_NUM)
+ for old_backup in ${old_backups[@]}; do
+ rm -rf "$SITE/$old_backup"
+ echo "[$(date '+%Y-%m-%d_%H:%M:%S')] Removed old backup: $SITE/$old_backup"
+ done
+ else
+ echo "[$(date '+%Y-%m-%d_%H:%M:%S')] Amount of stored backups in '$SITE' does not exceed retention policy [$RETAIN_NUM]. No old backups removed"
+ fi
+done
diff --git a/scriptlets/ct_file_handler.sh b/scriptlets/ct_file_handler.sh
new file mode 100755
index 0000000..2e34b1c
--- /dev/null
+++ b/scriptlets/ct_file_handler.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+if [[ $1 == *.sh ]]
+then
+ chmod +x "$1" && xfce4-terminal --hold --geometry=80x24 -e "/bin/bash -c $1" && chmod -x "$1"
+
+elif [[ $1 == *.py ]]
+then
+ xfce4-terminal --hold --geometry=80x24 -e "/bin/python3 $1" &
+else
+ xdg-open "$1" &
+fi \ No newline at end of file
diff --git a/scriptlets/ct_self_linker.sh b/scriptlets/ct_self_linker.sh
new file mode 100755
index 0000000..b0e9198
--- /dev/null
+++ b/scriptlets/ct_self_linker.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+link=$(readlink -- "$0")
+
+if [ -n "$link" ]; then
+ echo "$link"
+else
+ echo "gweh"
+fi
diff --git a/scriptlets/die-panel.sh b/scriptlets/die-panel.sh
new file mode 100755
index 0000000..f20fc9c
--- /dev/null
+++ b/scriptlets/die-panel.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+# Dependencies: bash>=3.2, coreutils, file
+
+# Makes the script more portable
+readonly DIR="$HOME/stuf/customization/"
+
+# Optional icon to display before the text
+# Insert the absolute path of the icon
+# Recommended size is 24x24 px
+declare -r ICON_ARRAY=(
+ "${DIR}/icons/dice/inverted-dice-1.png"
+ "${DIR}/icons/dice/inverted-dice-2.png"
+ "${DIR}/icons/dice/inverted-dice-3.png"
+ "${DIR}/icons/dice/inverted-dice-4.png"
+ "${DIR}/icons/dice/inverted-dice-5.png"
+ "${DIR}/icons/dice/inverted-dice-6.png"
+)
+
+# Compute random die
+DIE=$(( RANDOM % 6 ))
+
+# Panel
+if [[ $(file -b "${ICON_ARRAY[DIE]}") =~ PNG|SVG ]]; then
+ INFO="<img>${ICON_ARRAY[DIE]}</img><click>xfce4-panel --plugin-event=genmon-14:refresh:bool:true</click>"
+fi
+
+# Tooltip
+MORE_INFO="<tool>"
+MORE_INFO+="$(( DIE + 1 ))"
+MORE_INFO+="</tool>"
+
+# Panel Print
+echo -e "${INFO}"
+
+# Tooltip Print
+echo -e "${MORE_INFO}"
diff --git a/scriptlets/dl_helper_helper.sh b/scriptlets/dl_helper_helper.sh
new file mode 100755
index 0000000..33953e9
--- /dev/null
+++ b/scriptlets/dl_helper_helper.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+
+KEYBOARD=9
+KEYS="(37|72)"
+
+# ctrl=37, f6-72
+# find out the rest via xinput query-state <ID OF KEYBOARD>
+
+while [[ $( xinput query-state "$KEYBOARD" | grep -E "key\[${KEYS}\]=down" | wc -l ) -gt 0 ]] ; do
+ sleep 0.1
+done
+
+xdotool sleep 0.1 key shift+Down sleep 0.1 key shift+Up
+$HOME/stuf/scripts/dl_helper.sh "$(xclip -o -sel primary)" ; $HOME/stuf/scripts/notification_wrapper.sh $? "DLHELPER"
+
+# alternatively : xclip -sel clip < /dev/null
diff --git a/scriptlets/drun.sh b/scriptlets/drun.sh
new file mode 100755
index 0000000..5e9b46f
--- /dev/null
+++ b/scriptlets/drun.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+ROFI_THEME="ErikaScythe"
+
+rofi -show drun -display-drun "" -theme "$ROFI_THEME"
diff --git a/scriptlets/extractfiles.sh b/scriptlets/extractfiles.sh
new file mode 100755
index 0000000..8263103
--- /dev/null
+++ b/scriptlets/extractfiles.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+find $(pwd) -type f -exec mv '{}' . \; \ No newline at end of file
diff --git a/scriptlets/feh_action3.sh b/scriptlets/feh_action3.sh
new file mode 100755
index 0000000..d45de39
--- /dev/null
+++ b/scriptlets/feh_action3.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+ROFI_THEME="ErikaScythe2"
+
+NEW_IMAGE="$1"
+NEW_IMAGE_SIZE="$( du -sh "$NEW_IMAGE" | cut -f1 )"
+FILELIST="$2"
+[[ $( wc -l $FILELIST | cut -d' ' -f1 ) -ne 2 ]] && notify-send "ERROR: FILE LIST DOESN'T HAVE TWO FILES" && exit
+OLD_IMAGE="$( cat "$FILELIST" | grep -v "$NEW_IMAGE" | sort | head -n1 )"
+OLD_IMAGE_SIZE="$( du -sh "$OLD_IMAGE" | cut -f1 )"
+
+CHOICE=$(echo -e "Yes\nNo" \
+| rofi -dmenu -i -no-custom -p "$( echo -e "Replace image\n$OLD_IMAGE (${OLD_IMAGE_SIZE})\nwith\n$NEW_IMAGE (${NEW_IMAGE_SIZE})?" )" -theme "$ROFI_THEME" -async-pre-read 2 -no-click-to-exit )
+
+if [[ "$CHOICE" == "Yes" ]]; then
+ NEW_EXTENSION="${NEW_IMAGE##*.}"
+ OLD_BASE="${OLD_IMAGE%.*}"
+ NEW_FINAL="${OLD_BASE}.${NEW_EXTENSION}"
+ gio trash "$OLD_IMAGE" && mv "${NEW_IMAGE}" "${NEW_FINAL}"
+fi
diff --git a/scriptlets/find_papes.sh b/scriptlets/find_papes.sh
new file mode 100755
index 0000000..812c0f6
--- /dev/null
+++ b/scriptlets/find_papes.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+scale=5
+DIR="$( readlink -f "$1" )"
+[[ -d "$DIR" ]] || { echo "NEED DIRECTORY NAME"; exit -1; }
+
+while true; do
+ read -p "Enter resolution of pape (WxH, e.g. 1920x1080): " res
+
+ [[ "$res" =~ [0-9]+x[0-9]+ ]] && break
+
+ echo "Wrong input"
+done
+
+X=${res%x*}
+Y=${res#*x}
+
+[[ $X -eq 0 || $Y -eq 0 ]] && echo "Don't fuck around" && exit
+res=$( echo "scale = $scale; $X / $Y" | bc )
+upper=$( echo "scale = $scale; $res + 0.05" | bc )
+lower=$( echo "scale = $scale; $res - 0.05" | bc )
+
+echo "$upper --- $lower"
+
+candidates="$( mktemp --tmpdir=$HOME/Desktop )"
+
+find "$DIR" -type f -iregex ".*\.\(bmp\|gif\|jpg\|jpeg\|png\)$" -print0 | while read -d $'\0' pic; do
+
+ res=$( identify "$pic" | grep -o --extended-regexp --ignore-case "[0-9]+x[0-9]+" | head -1 | sed -r "s|x|\ / |; s|^|scale = $scale; |" | bc )
+ if (( $(echo "$upper >= $res" | bc -l) && $(echo "$lower <= $res" | bc -l) )); then echo "$pic" >> $candidates; fi
+
+done
+
+monitor="$( xfconf-query -c xfce4-desktop -l | grep "workspace0/last-image" | dmenu -l 5 )"
+
+feh --info "printf '%S %wx%h'" --zoom max --scale-down -g 1280x720 -B black -d --action1 "cp %F $HOME/Desktop" --action2 "nohup gimp -a %F >/dev/null 2>&1 &" --action3 "xfconf-query -c xfce4-desktop -p $monitor -s %F" -f "$candidates"
+
+rm "$candidates"
diff --git a/scriptlets/findlast.sh b/scriptlets/findlast.sh
new file mode 100755
index 0000000..32d5c92
--- /dev/null
+++ b/scriptlets/findlast.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# THUNAR CUSTOM ACTION
+
+# Name: Set Art lookup directory
+# Description: Set the current directory as the one findlast.sh will check
+
+# Command: $HOME/stuf/scripts/scriptlets/findlast.sh --set-artdir
+# Keyboard Shortcut: Shift+F2
+# Icon: shupogaki.png
+
+# Appearance Conditions -> Check "Directories **"
+
+if [[ "$1" = "--set-artdir" ]]; then
+ echo $(pwd) > $HOME/stuf/artdir.txt
+ $HOME/stuf/scripts/notification_wrapper.sh "Set $(pwd) as the new artdir." "FINDLAST"
+else
+ ARTDIR="$( head -n1 "$HOME/stuf/artdir.txt" )"
+ SELECTION="$( xclip -o -sel primary | sed -r "s|\.[a-zA-Z]+||g" )"
+ [[ -z $SELECTION ]] && exit -1
+
+ $HOME/stuf/scripts/notification_wrapper.sh "$( find "$ARTDIR" -type f -iregex ".*/${SELECTION}[0-9]+[a-zA-Z]*\.[a-zA-Z]+" | sort -V | tail -n1 )" "FINDLAST"
+fi
diff --git a/scriptlets/glava_toggler.sh b/scriptlets/glava_toggler.sh
new file mode 100755
index 0000000..28ae226
--- /dev/null
+++ b/scriptlets/glava_toggler.sh
@@ -0,0 +1,6 @@
+#/bin/bash
+
+command="glava --desktop"
+[[ -n $( pgrep -f "$command" ) ]] && pkill -f "$command" || nohup $command > /dev/null 2>&1 &
+
+
diff --git a/scriptlets/ieset.sh b/scriptlets/ieset.sh
new file mode 100755
index 0000000..698dbe4
--- /dev/null
+++ b/scriptlets/ieset.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+while read LINE; do
+
+ P_AMOUNT="$( echo $line | sed -r "s|\s+([0-9]+) [^ ]+|\1|g" )"
+ echo "$P_AMOUNT"
+
+done < <( find "$(pwd)" -type f | sort | sed -r "s|_p[0-9]+||g" | uniq -c )
diff --git a/scriptlets/ikaloop.sh b/scriptlets/ikaloop.sh
new file mode 100755
index 0000000..40e6cad
--- /dev/null
+++ b/scriptlets/ikaloop.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+CLIP="$HOME/1Misc/[DUWANG]_Shinryaku!_Ika_Musume-_01[240p].wmv.avi"
+
+mpv --loop --no-audio "$CLIP" & disown \ No newline at end of file
diff --git a/scriptlets/make_exceptions.txt b/scriptlets/make_exceptions.txt
new file mode 100644
index 0000000..2123225
--- /dev/null
+++ b/scriptlets/make_exceptions.txt
@@ -0,0 +1 @@
+for file in $(ls | grep -oE "[^_]+_p[1-9]+\..*$" | sed -r "s|_.*||g" | sort | uniq); do ls *$file* | sort | sed -r "s|^|$(pwd)\/|g" | tr "\n" " " >> exceptions.txt ; echo -e "" >> exceptions.txt; done
diff --git a/scriptlets/mute_button.sh b/scriptlets/mute_button.sh
new file mode 100644
index 0000000..7a693aa
--- /dev/null
+++ b/scriptlets/mute_button.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+
diff --git a/scriptlets/old_cliparmyknife.sh b/scriptlets/old_cliparmyknife.sh
new file mode 100755
index 0000000..8636a84
--- /dev/null
+++ b/scriptlets/old_cliparmyknife.sh
@@ -0,0 +1,295 @@
+#!/bin/bash
+
+#set -e
+
+get_clip_params () {
+
+ CLIP_PATH=$( dirname "$1" )
+ CLIP_NAME=$( basename "$1" | sed -r "s|\.[^. ]*$||g" )
+ CLIP_EXTENSION=$( sed -r "s|^.*\.||g" <<< "$1" )
+ CLIP_WIDTH=$( ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "$1" )
+ CLIP_HEIGHT=$( ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$1" )
+ CLIP_SAR=$( ffprobe -v error -select_streams v:0 -show_entries stream=sample_aspect_ratio -of default=noprint_wrappers=1:nokey=1 "$1" )
+ CLIP_FRAMERATE=$( ffprobe -i "$1" 2>&1 | grep -oP "\d{1,5}\.?\d{1,5} fps" | tail -1 | sed -r "s|\ fps||g" )
+
+ MOD=1
+
+ [[ "$CLIP_SAR" != "1:1" ]] && MOD=$( echo "$CLIP_SAR" | sed -r "s|\:|\ \/\ |g; s|^|scale\=5\;\ |g" | bc ) && CLIP_HEIGHT=$( printf "%.0f" $( bc <<< "$CLIP_HEIGHT / $MOD" ) )
+
+}
+
+dump_clip_params () {
+
+ echo -e "\nCLIP NAME: $CLIP_NAME.$CLIP_EXTENSION\nCLIP LOCATION: $CLIP_PATH/\nCLIP DIMENSIONS: ${CLIP_WIDTH}x${CLIP_HEIGHT}\nCLIP FRAMERATE: $CLIP_FRAMERATE\nCLIP SAR: $CLIP_SAR"
+
+}
+
+crop () {
+
+ echo -e "-----out_w is the width of the output rectangle-----\n-----out_h is the height of the output rectangle-----\n-----x and y specify the top left corner of the output rectangle-----"
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: ${CLIP_WIDTH}x${CLIP_HEIGHT}"
+
+
+
+ re='^[0-9]+(\ )+[0-9]+(\ )+[0-9]+(\ )+[0-9]+$'
+
+ declare OUT_W OUT_H X Y CROP_PARAMS
+
+ while read -p "Enter new clip dimensions [FORMAT: out_w out_h x y]: " CROP_PARAMS; do
+
+ CROP_PARAMS=${CROP_PARAMS:-"$CLIP_WIDTH $CLIP_HEIGHT 0 0"}
+
+ if ! [[ $CROP_PARAMS =~ $re ]]; then
+ echo "error: Invalid crop parameters" >&2; continue
+ fi
+
+
+
+ OUT_W=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 1 )
+ OUT_H=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 2 )
+ X=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 3 )
+ Y=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 4 )
+
+ if [[ $( bc <<< "$OUT_W + $X" ) -gt $CLIP_WIDTH || $( bc <<< "$OUT_H + $Y" ) -gt $CLIP_HEIGHT ]]; then
+ echo "error: Crop parameters go outside clip bounds" >&2; continue
+ fi
+
+ break
+ done
+
+ OUT_H_MOD=$( bc <<< "$OUT_H * $MOD" )
+ Y_MOD=$( bc <<< "$Y * $MOD" )
+
+ ffmpeg -i "$1" -filter:v "crop=$OUT_W:$OUT_H_MOD:$X:$Y_MOD" "$CLIP_NAME [cropped $OUT_W:$OUT_H:$X:$Y].$CLIP_EXTENSION"
+
+}
+
+join_no_reencode () {
+ [[ ! -f "join.$CLIP_EXTENSION" ]] && cp "$1" "join.$CLIP_EXTENSION" && return
+ echo "file 'join.${CLIP_EXTENSION}'" > cliparmyknife_concat.txt
+ echo "file '$1'" >> cliparmyknife_concat.txt
+ ffmpeg -y -f concat -safe 0 -i cliparmyknife_concat.txt -c copy join.$CLIP_EXTENSION
+ rm cliparmyknife_concat.txt
+}
+
+join_yes_reencode () {
+
+
+ [[ ! -f "join.$CLIP_EXTENSION" ]] && cp "$1" "join.$CLIP_EXTENSION" && return
+ JOIN_WIDTH=$( ffprobe -i "join.$CLIP_EXTENSION" 2>&1 | grep -oP "[1-9]\d{1,10}x[1-9]\d{1,10}" | sed -r "s|\ ||g; s|x[0-9]*$||g" )
+ JOIN_HEIGHT=$( ffprobe -i "join.$CLIP_EXTENSION" 2>&1 | grep -oP "[1-9]\d{1,10}x[1-9]\d{1,10}" | sed -r "s|\ ||g; s|^[0-9]*x||g" )
+
+ #ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i boximouto.webm -c:v copy -c:a libopus -shortest boximouto2.webm
+ #ffprobe -i ikadiabetes2.gif -show_streams -select_streams a -loglevel error
+
+ [[ -z $(ffprobe -i "$1" -show_streams -select_streams a -loglevel error) ]] && ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "$1" -c:v copy -c:a libopus -shortest "/tmp/clip_treated.$CLIP_EXTENSION" || cp "$1" "/tmp/clip_treated.$CLIP_EXTENSION"
+ [[ -z $(ffprobe -i "join.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) ]] && ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "join.$CLIP_EXTENSION" -c:v copy -c:a libopus -shortest "/tmp/join_treated.$CLIP_EXTENSION" || cp "join.$CLIP_EXTENSION" "/tmp/join_treated.$CLIP_EXTENSION"
+
+
+ echo "JOIN DIMENSIONS: $JOIN_WIDTH x $JOIN_HEIGHT"
+
+ [[ $JOIN_WIDTH -gt $CLIP_WIDTH ]] && PAD_WIDTH=$JOIN_WIDTH || PAD_WIDTH=$CLIP_WIDTH
+ [[ $JOIN_HEIGHT -gt $CLIP_HEIGHT ]] && PAD_HEIGHT=$JOIN_HEIGHT || PAD_HEIGHT=$CLIP_HEIGHT
+ PAD_X=$( bc <<< "($JOIN_WIDTH - $CLIP_WIDTH) / 2" )
+ PAD_Y=$( bc <<< "($JOIN_HEIGHT - $CLIP_HEIGHT) / 2" )
+
+ echo "PAD DIMENSIONS: $PAD_X, $PAD_Y"
+
+ if [[ $PAD_X -ge 0 && $PAD_Y -ge 0 ]]; then
+ cp "/tmp/join_treated.$CLIP_EXTENSION" "/tmp/join_temp.$CLIP_EXTENSION"
+ ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=$PAD_X:y=$PAD_Y:color=black" "/tmp/clip_temp.$CLIP_EXTENSION"
+ elif [[ $PAD_X -lt 0 && $PAD_Y -ge 0 ]]; then
+ ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=${PAD_X#-}:y=0:color=black" "/tmp/join_temp.$CLIP_EXTENSION"
+ ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=0:y=$PAD_Y:color=black" "/tmp/clip_temp.$CLIP_EXTENSION"
+ elif [[ $PAD_X -ge 0 && $PAD_Y -lt 0 ]]; then
+ ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=0:y=${PAD_Y#-}:color=black" "/tmp/join_temp.$CLIP_EXTENSION"
+ ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=$PAD_X:y=0:color=black" "/tmp/clip_temp.$CLIP_EXTENSION"
+ elif [[ $PAD_X -lt 0 && $PAD_Y -lt 0 ]]; then
+ ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=${PAD_X#-}:y=${PAD_Y#-}:color=black" "/tmp/join_temp.$CLIP_EXTENSION"
+ cp "/tmp/clip_treated.$CLIP_EXTENSION" "/tmp/clip_temp.$CLIP_EXTENSION"
+ else
+ echo "something fucked up" && exit 1
+ fi
+
+
+ if [[ -n $(ffprobe -i "/tmp/clip_temp.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) && -n $(ffprobe -i "/tmp/clip_temp.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) ]]; then
+ ffmpeg -y -i "/tmp/join_temp.$CLIP_EXTENSION" -i "/tmp/clip_temp.$CLIP_EXTENSION" -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" "/tmp/join2.$CLIP_EXTENSION"
+ else
+ ffmpeg -y -i "/tmp/join_temp.$CLIP_EXTENSION" -i "/tmp/clip_temp.$CLIP_EXTENSION" -filter_complex "[0:v:0] [1:v:0]concat=n=2:v=1[outv]" -map "[outv]" "/tmp/join2.$CLIP_EXTENSION"
+ fi
+
+ mv "/tmp/join2.$CLIP_EXTENSION" "join.$CLIP_EXTENSION"
+ rm "/tmp/join_treated.$CLIP_EXTENSION" "/tmp/clip_treated.$CLIP_EXTENSION" "/tmp/join_temp.$CLIP_EXTENSION" "/tmp/clip_temp.$CLIP_EXTENSION"
+}
+
+join () {
+
+ if [[ "$REENCODE_FLAG" = "true" ]]; then
+ join_yes_reencode "$1"
+ elif [[ "$REENCODE_FLAG" = "false" ]]; then
+ join_no_reencode "$1"
+ else
+
+ while true; do
+ read -p "Are the video parameters identical (y/n)? " yn
+
+ case $( tr '[A-Z]' '[a-z]' <<< "$yn" ) in
+ y|yes) REENCODE_FLAG="false"; join_no_reencode "$1" ; break;;
+ n|no) REENCODE_FLAG="true"; join_yes_reencode "$1" ; break;;
+ *) echo "Invalid response." ;;
+ esac
+ done
+ fi
+}
+
+mute () {
+
+ ffmpeg -i "$1" -c copy -an "$CLIP_NAME [no audio].$CLIP_EXTENSION"
+
+}
+
+extract_audio () {
+
+ ffmpeg -i "$1" -codec:a libmp3lame "$CLIP_NAME [audio only].mp3"
+
+}
+
+scale () {
+
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT"
+ while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do
+
+ SCALE=${SCALE:-2}
+
+ re='^[0-9]*([.][0-9]+)?$'
+ if ! [[ $SCALE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ ffmpeg -i "$1" -vf scale="-1:$( printf "%.0f" $( bc <<< "scale=5; $CLIP_HEIGHT / $SCALE" ) )" -c:a copy "$CLIP_NAME [$SCALE].$CLIP_EXTENSION"
+
+}
+
+reverse () {
+
+ ffmpeg -i "$1" -vf reverse -af areverse "$CLIP_NAME [reversed].$CLIP_EXTENSION"
+
+}
+
+clip_to_gif () {
+
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT"
+ while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do
+
+ SCALE=${SCALE:-2}
+
+ re='^[0-9]*([.][0-9]+)?$'
+ if ! [[ $SCALE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ echo -e "-----CURRENT FRAMERATE OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_FRAMERATE fps"
+ while read -p "Enter new framerate [Default: 23.98; Recommended: $( bc <<< "scale=2; $CLIP_FRAMERATE / 2" )]: " RATE; do
+
+ RATE=${RATE:-23.98}
+
+ re='^[1-9][0-9]*([.][0-9]+)?$'
+ if ! [[ $RATE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ ffmpeg -y -i "$1" -vf palettegen "/tmp/_tmp_palette_$CLIP_NAME.png"
+ ffmpeg -y -i "$1" -i "/tmp/_tmp_palette_$CLIP_NAME.png" -filter_complex paletteuse -r "$RATE" "/tmp/tmp_out_$CLIP_NAME.gif"
+ rm "/tmp/_tmp_palette_$CLIP_NAME.png"
+ gifsicle --optimize=3 --no-background --output "$CLIP_NAME [$SCALE - $RATE].gif" --resize "$( bc <<< "$CLIP_WIDTH / $SCALE" )x$( bc <<< "$CLIP_HEIGHT / $SCALE" )" "/tmp/tmp_out_$CLIP_NAME.gif"
+ rm "/tmp/tmp_out_$CLIP_NAME.gif"
+
+}
+
+clip_to_webm () {
+
+ echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT"
+ while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do
+
+ SCALE=${SCALE:-2}
+
+ re='^[1-9][0-9]*([.][0-9]+)?$'
+ if ! [[ $SCALE =~ $re ]] ; then
+ echo "error: Not a non-zero number" >&2; continue
+ fi
+
+ break
+ done
+
+ ffmpeg -i "$1" -c:v libvpx-vp9 -crf 30 -b:v 0 -b:a 128k -vf scale="-1:$( bc <<< "$CLIP_HEIGHT / $SCALE" )" -c:a libopus "$CLIP_NAME [$SCALE].webm"
+}
+
+webp_to_gif () {
+ python3 -c "from PIL import Image;Image.open('$1').save('${1%.webp}.gif','gif',save_all=True,optimize=True,background=0)"
+}
+
+FUNCTION=""
+
+while getopts "hicsrjmxgw" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-i get clip info] [-c crop clip] [-s scale/resize clip] [-r reverse clip] [-j join clips] [-m mute clip] [-x extract audio] [-g convert clip to gif] [-w convert clip to webm]"; exit ;;
+ i) FUNCTION="dump_clip_params"; break;;
+ c) FUNCTION="crop"; break ;;
+ s) FUNCTION="scale"; break ;;
+ r) FUNCTION="reverse"; break ;;
+ j) FUNCTION="join"; break ;;
+ m) FUNCTION="mute"; break ;;
+ x) FUNCTION="extract_audio"; break ;;
+ g) FUNCTION="convert"; break ;;
+ w) FUNCTION="clip_to_webm"; break ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+if [ "$#" -lt 2 ]; then
+ echo "Please insert only one flag and at least one argument"
+ exit
+else
+ echo -e "\c"
+fi
+
+i=0
+
+for file in "${@:2}"
+do
+ re='s|\.[^. ]*$||g'
+
+ ! [[ $file =~ $re ]] && echo "$file NOT A PROPER FILE, IGNORING..." && continue
+ get_clip_params "$file"
+ echo "$CLIP_NAME - $CLIP_EXTENSION - $FUNCTION"
+
+ if [[ "$FUNCTION" == "convert" ]]; then
+
+ if [[ "$CLIP_EXTENSION" == "gif" ]]; then
+ echo -e "$CLIP_NAME.$CLIP_EXTENSION IS ALREADY A GIF. SKIPPING...\n\n-------------------------$file DONE-------------------------\n\n" && continue
+ elif [[ "$CLIP_EXTENSION" == "webp" ]]; then
+ FUNCTION="webp_to_gif"
+ else
+ FUNCTION="clip_to_gif"
+ fi
+
+ fi
+
+ $FUNCTION "$file"
+ [[ "$FUNCTION" =~ ^(clip|webp)_to_gif$ ]] && FUNCTION="convert"
+ echo -e "\n\n------------------------- $file - DONE -------------------------\n\n"
+done
+
+
+# TO ADD: MORE FLEXIBLE FLAG/ARGUMENT PROCESSING, FLAG WHICH MAKES EVERY FUNCTION RESPECT FILEPATHS (I.E. DOESN'T DRAG EVERY OUTPUT TO CURRENT DIRECTORY)
+
+
diff --git a/scriptlets/old_veepeen_toggler.sh b/scriptlets/old_veepeen_toggler.sh
new file mode 100755
index 0000000..ad48082
--- /dev/null
+++ b/scriptlets/old_veepeen_toggler.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+passentry="shigoto/vpn"
+CONN_NAME="cscotun0"
+
+if [ -n "$(ip address show | grep -iE "^[0-9]+: $CONN_NAME")" ]; then
+ echo "ALREADY CONNECTED. DISCONNECTING..."
+ /opt/cisco/anyconnect/bin/vpn disconnect
+else
+ CONN_HOST="$( pass $passentry | grep "url: "| sed -r "s|url: ||g" )"
+ CONN_CREDS_USERNAME="$( pass $passentry | grep "username: "| sed -r "s|username: ||g" )"
+ CONN_CREDS_PASSWORD="$( pass $passentry | head -n1 )"
+ echo "CONNECTING... DON'T FORGET YOUR PHONE VERIFICATION"
+ printf '%s\n%s' "$CONN_CREDS_USERNAME" "$CONN_CREDS_PASSWORD" | /opt/cisco/anyconnect/bin/vpn -s connect "$CONN_HOST"
+fi
+
+: '
+Note the single quotes instead of double quotes - this is because double quotes tell Bash to interpret certain characters within strings, such as exclamation marks, as Bash history commands. Double quotes will make this command fail with an "event not found" error if the password contains an exclamation mark. Single-quoted strings pass exclamation marks along without interpreting them.
+
+In case your client does not connect due to certificate validation error Certificate is from an untrusted source,
+and you still want to connect then pass a y parameter in the above method so that the command to connect becomes:
+printf "y\nUSERNAME\nPASSWORD\ny" | /opt/cisco/anyconnect/bin/vpn -s connect HOST.
+Note that do this only in the case that you absolutely trust your connection;
+otherwise there might be a middleman sitting in and snooping onto you.
+'
diff --git a/scriptlets/passmenu b/scriptlets/passmenu
new file mode 100755
index 0000000..7b5b908
--- /dev/null
+++ b/scriptlets/passmenu
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+shopt -s nullglob globstar
+
+# dumb fucking keyboard shortcut won't with my default env variables
+PASSWORD_STORE_DIR="$HOME/stuf/password-store"
+
+typeit=0
+if [[ $1 == "--type" ]]; then
+ typeit=1
+ shift
+fi
+
+if [[ -n $WAYLAND_DISPLAY ]]; then
+ dmenu=dmenu-wl
+ xdotool="ydotool type --file -"
+elif [[ -n $DISPLAY ]]; then
+ dmenu=dmenu
+ xdotool="xdotool type --clearmodifiers --file -"
+else
+ echo "Error: No Wayland or X11 display detected" >&2
+ exit 1
+fi
+
+prefix=${PASSWORD_STORE_DIR-~/.password-store}
+password_files=( "$prefix"/**/*.gpg )
+password_files=( "${password_files[@]#"$prefix"/}" )
+password_files=( "${password_files[@]%.gpg}" )
+
+password=$(printf '%s\n' "${password_files[@]}" | "$dmenu" "$@")
+
+[[ -n $password ]] || exit
+
+if [[ $typeit -eq 0 ]]; then
+ pass show -c "$password" 2>/dev/null
+else
+ pass show "$password" | { IFS= read -r pass; printf %s "$pass"; } | $xdotool
+fi
diff --git a/scriptlets/passshowcopy.sh b/scriptlets/passshowcopy.sh
new file mode 100644
index 0000000..016e492
--- /dev/null
+++ b/scriptlets/passshowcopy.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "under construction..."
diff --git a/scriptlets/peak_or_mid.sh b/scriptlets/peak_or_mid.sh
new file mode 100755
index 0000000..c5047ab
--- /dev/null
+++ b/scriptlets/peak_or_mid.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+
+DIR="$( readlink -f "$1" )"
+[[ -d "$DIR" ]] || { echo "NEED DIRECTORY NAME"; exit -1; }
+
+feh --sort filename --version-sort --info "printf '%S %wx%h'" --zoom max --scale-down -g 1280x720 -B black -d --action1 "gio trash %F" --action2 "[ -d \"${DIR}/peak\" ] || mkdir \"${DIR}/peak\"; mv %F \"${DIR}/peak\"" --action3 "[ -d \"${DIR}/mid\" ] || mkdir \"${DIR}/mid\"; mv %F \"${DIR}/mid/\"" "$DIR"
diff --git a/scriptlets/qemu-android.sh b/scriptlets/qemu-android.sh
new file mode 100755
index 0000000..f71e303
--- /dev/null
+++ b/scriptlets/qemu-android.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+set -xe
+
+IMAGE="$1"
+
+shift
+
+qemu-system-x86_64 -boot d \
+ -enable-kvm \
+ -smp 2 \
+ -name linuz \
+ -device virtio-gpu-gl,xres=1280,yres=720 \
+ -cpu host \
+ -device AC97 \
+ -m 2048 \
+ -display gtk,gl=on \
+ -drive file="$IMAGE",if=virtio \
+ -object rng-random,id=rng0,filename=/dev/urandom \
+ -device virtio-rng-pci,rng=rng0 \
+ -device virtio-keyboard \
+ -boot menu=off \
+ -device virtio-tablet \
+ -machine type=q35 \
+ -serial mon:stdio \
+ -net nic -net user,hostfwd=tcp::4444-:5555 \
+ -cdrom "$@"
diff --git a/scriptlets/qemu-run.sh b/scriptlets/qemu-run.sh
new file mode 100755
index 0000000..b972140
--- /dev/null
+++ b/scriptlets/qemu-run.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+set -xe
+
+IMAGE="$1"
+
+shift
+
+qemu-system-x86_64 \
+-accel kvm \
+-M q35 \
+-m 4096 -smp 4 -cpu host \
+-bios /usr/share/ovmf/x64/OVMF.fd \
+-drive file="$IMAGE",if=virtio \
+-usb \
+-device virtio-tablet \
+-device virtio-keyboard \
+-device qemu-xhci,id=xhci \
+-machine vmport=off \
+-device virtio-vga-gl,xres=1280,yres=720 -display sdl,gl=on \
+-audiodev pa,id=snd0 -device AC97,audiodev=snd0 \
+-net nic,model=virtio-net-pci -net user,hostfwd=tcp::4444-:5555 \
+-cdrom "$@" \
+
diff --git a/scriptlets/rss_torrent_helper.sh b/scriptlets/rss_torrent_helper.sh
new file mode 100755
index 0000000..bea25e4
--- /dev/null
+++ b/scriptlets/rss_torrent_helper.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# this helper script is a monument to my personal failings
+# a better man would've just modified the parsing in the original script to allow for per-feed downloaders
+# but alas
+
+
+CONFIG_DIR="$HOME/.config/shell-rss-torrent"
+
+for file in $CONFIG_DIR/*.xml; do
+ $HOME/stuf/scripts/shell-rss-torrent "$file"
+done \ No newline at end of file
diff --git a/scriptlets/rxvt_nvim_launcher.sh b/scriptlets/rxvt_nvim_launcher.sh
new file mode 100755
index 0000000..c7a7014
--- /dev/null
+++ b/scriptlets/rxvt_nvim_launcher.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+FILE="$1"
+[[ -z "$1" ]] && echo "NO" && exit -1
+
+/usr/bin/urxvt -geometry 160x48 -title "Terminal - Nvim ($FILE)" -e sh -c "/usr/bin/nvim \"$FILE\""
diff --git a/scriptlets/rxvt_yazi_launcher.sh b/scriptlets/rxvt_yazi_launcher.sh
new file mode 100755
index 0000000..7bd2824
--- /dev/null
+++ b/scriptlets/rxvt_yazi_launcher.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+DIR="$1"
+EDITOR=/usr/bin/nvim
+
+if [[ ! -d "$DIR" ]]; then
+ /usr/bin/urxvt -geometry 160x48 -e sh -c "/usr/bin/yazi"
+else
+ /usr/bin/urxvt -geometry 160x48 -e sh -c "/usr/bin/yazi ${DIR}"
+fi
diff --git a/scriptlets/sidebar_toggler.sh b/scriptlets/sidebar_toggler.sh
new file mode 100755
index 0000000..8ebdde5
--- /dev/null
+++ b/scriptlets/sidebar_toggler.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+PANELNUM=2
+xfconf-query -c xfce4-panel -p "/panels/panel-$PANELNUM/autohide-behavior" -s $(( $( xfconf-query -c xfce4-panel -p "/panels/panel-$PANELNUM/autohide-behavior" ) ^ 2 )) \ No newline at end of file
diff --git a/scriptlets/soundpost_dl.sh b/scriptlets/soundpost_dl.sh
new file mode 100755
index 0000000..28947b6
--- /dev/null
+++ b/scriptlets/soundpost_dl.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+WORKDIR="$HOME"
+
+URL="$1"
+REGEX="https:\/\/boards\.4chan\.org\/([a-z]+)\/thread\/([0-9]+)#p([0-9]+)"
+
+if [[ $URL =~ $REGEX ]]; then
+ BOARD="${BASH_REMATCH[1]}"
+ THREAD="${BASH_REMATCH[2]}"
+ POST="${BASH_REMATCH[3]}"
+else
+ echo "ERROR. BAD URL" && exit -1
+fi
+
+API_CALL="https://a.4cdn.org/${BOARD}/thread/${THREAD}.json"
+POST_JSON="$( curl -s "$API_CALL" | jq -c ".posts[] | select( .no == ${POST} )" )"
+
+video="$( jq --argjson post "$POST_JSON" -n '$post.tim' )$( jq --argjson post "$POST_JSON" -n '$post.ext' | tr -d '"' )"
+audio="$( jq --argjson post "$POST_JSON" -n '$post.filename' | sed -r "s|.*files\.catbox\.moe%2F([a-zA-Z0-9%\.]+).*|\1|g" )"
+
+video_file="/tmp/${video}"
+audio_file="/tmp/${audio}"
+
+video_url="https://i.4cdn.org/${BOARD}/${video}"
+audio_url="https://files.catbox.moe/${audio}"
+
+output="${WORKDIR}/${video}"
+
+echo -e "\nWriting to ${output}...\n"
+
+yt-dlp "$video_url" -o "$video_file" && wget "$audio_url" -O "$audio_file"
+
+ffmpeg -y -v 24 -i "${audio_file}" -c:a libvorbis -q:a 4 "${audio_file%%.*}.ogg" && rm -f "${audio_file}"
+ffmpeg -y -v 24 -i "${video_file}" -i "${audio_file%%.*}.ogg" -c:v copy -c:a libvorbis -filter_complex "[1:0]apad" -shortest "${output}" && rm -f "$video_file" && rm -f "$audio_file"
+
+echo -e "\nDone.\n"
+
diff --git a/scriptlets/spacebarspam.sh b/scriptlets/spacebarspam.sh
new file mode 100755
index 0000000..180e9a1
--- /dev/null
+++ b/scriptlets/spacebarspam.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+while : ; do
+
+ sleep 0.05 && xdotool key 'space'
+
+done
+
diff --git a/scriptlets/springcleaning.sh b/scriptlets/springcleaning.sh
new file mode 100755
index 0000000..781a0b9
--- /dev/null
+++ b/scriptlets/springcleaning.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+
+# keeping a fresh list of all explicitly installed packages
+
+pacman -Qqe > "/home/jay/stuf/ARESEES/pkglist.txt"
+pacman -Qqen > "/home/jay/stuf/ARESEES/pkglist_native.txt"
+pacman -Qqem > "/home/jay/stuf/ARESEES/pkglist_foreign.txt"
+
+# pacman -S --needed - < pkglist.txt to install them
+# pacman -S --needed $(comm -12 <(pacman -Slq | sort) <(sort pkglist.txt)) to filter out the AUR ones
+# pacman -Rsu $(comm -23 <(pacman -Qq | sort) <(sort pkglist.txt)) final check to make sure only listed packages are present on the system
+
+# -----------------------------------------------------------------------------------------------------------------------------
+
+# removing all but 3 most recent package versions
+
+paccache -r
+paccache -ruk0 # every version if said package is uninstalled
+
+# you can enable/start paccache.timer for the first one to happen automatically
+
+# -----------------------------------------------------------------------------------------------------------------------------
+
+# removing orphaned packages
+
+pacman -Qtdq | pacman -Rns -
+
+# will throw an error if list is empty, this is fine
+# pacman -Qqd | pacman -Rsu --print - does the same as above(?) but also hits circular and excessive dependencies
+
+# -----------------------------------------------------------------------------------------------------------------------------
+
+# figure something out for dotfiles (google: rmshit.py)
diff --git a/scriptlets/thunar_batch_opener.sh b/scriptlets/thunar_batch_opener.sh
new file mode 100755
index 0000000..04fe54d
--- /dev/null
+++ b/scriptlets/thunar_batch_opener.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+browser="thunar"
+winfile="$HOME/stuf/windows.txt"
+mapfile -t < "$winfile"
+
+SAVEIFS=$IFS
+
+FLAG=0
+IFS=$'\n'
+while IFS= read -r tab
+do
+
+ [[ -z "$tab" ]] && FLAG=1 && continue
+
+ if [[ $FLAG -eq 1 ]]; then
+ FLAG=0
+ xfconf-query -c thunar -p /misc-open-new-window-as-tab -s false
+ else
+ xfconf-query -c thunar -p /misc-open-new-window-as-tab -s true
+ fi
+ "$browser" "$( sed -r "s|\\\$HOME|$HOME|g" <<< "$tab" )"
+ sleep 0.5
+ FLAG=0
+done < "$winfile"
+IFS=$SAVEIFS
+xfconf-query -c thunar -p /misc-open-new-window-as-tab -s true
diff --git a/scriptlets/transmission_bell.sh b/scriptlets/transmission_bell.sh
new file mode 100755
index 0000000..284fd05
--- /dev/null
+++ b/scriptlets/transmission_bell.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+SOUNDS="$HOME/stuf/customization/sounds"
+
+nohup play "$SOUNDS/bell.mp3" >/dev/null 2>&1 &
+
+
diff --git a/scriptlets/transmission_dirchooser.sh b/scriptlets/transmission_dirchooser.sh
new file mode 100755
index 0000000..d7614ab
--- /dev/null
+++ b/scriptlets/transmission_dirchooser.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+START_DIR="$HOME/Downloads"
+ROFI_THEME="ErikaScythe"
+# CHANGE THEME
+
+DIR="$START_DIR"
+FULL_DIR="$DIR"
+while [[ -n $DIR && $DIR != "." ]]; do
+ DIR="$( { echo -e ".\n.."; find "${FULL_DIR}" -mindepth 1 -maxdepth 1 -type d | sed -r "s|^.*\/||g" | sort -V ; } | rofi -dmenu -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 3 -no-click-to-exit )"
+ FULL_DIR="${FULL_DIR}/${DIR}"
+done
+
+[[ -z "$DIR" ]] && exit -1
+
+
+for file in "$@"; do
+ transmission-remote -a "$file" -w "$FULL_DIR"
+done
+
diff --git a/scriptlets/treediff.sh b/scriptlets/treediff.sh
new file mode 100755
index 0000000..3537e1f
--- /dev/null
+++ b/scriptlets/treediff.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+rsync -r --ignore-existing -i -n "$1" "$2"
diff --git a/scriptlets/truth.sh b/scriptlets/truth.sh
new file mode 100755
index 0000000..257c6a7
--- /dev/null
+++ b/scriptlets/truth.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+TEXT="$(xclip -o -sel clip)"
+TRUTH=""
+
+REDTRUTH='\e[2;31m'
+BLUETRUTH='\e[2;34m'
+GOLDENTRUTH='\e[2;33m'
+
+while getopts "hrbg" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-r red truth] [-b blue truth] [-g golden truth]" ;;
+ r) TRUTH='```ansi\n'${REDTRUTH}${TEXT}'\e[0m\n```' ;;
+ b) TRUTH='```ansi\n'${BLUETRUTH}${TEXT}'\e[0m\n```' ;;
+ g) TRUTH='```ansi\n'${GOLDENTRUTH}${TEXT}'\e[0m\n```' ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+echo -e $TRUTH | xclip -sel clip
diff --git a/scriptlets/unwebp.sh b/scriptlets/unwebp.sh
new file mode 100755
index 0000000..49d8f5a
--- /dev/null
+++ b/scriptlets/unwebp.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+find "$PWD" -type f -name '*webp' -print0 | \
+ while IFS= read -r -d '' file; do
+ newfile=$( echo "$file" | sed -r "s|nhentai_[0-9]+_0||g; s|\.webp$|.png|")
+ printf '%s ---> %s\n' "$file" "$newfile"
+ magick "$file" "$newfile" && rm -f "$file"
+ done
diff --git a/scriptlets/url_processor.sh b/scriptlets/url_processor.sh
new file mode 100755
index 0000000..98ae663
--- /dev/null
+++ b/scriptlets/url_processor.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+BROWSER="chromium"
+FLAGS="--profile-directory=Default"
+LINK=$( sed -r "s|youtube\.com|yewtu\.be|g" <<< "$1" )
+
+$BROWSER "$FLAGS" "$LINK"
+
diff --git a/scriptlets/veepeen_toggler.sh b/scriptlets/veepeen_toggler.sh
new file mode 100755
index 0000000..2e9dd1d
--- /dev/null
+++ b/scriptlets/veepeen_toggler.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+declare -A connections=( ["shigoto"]="shigoto/vpn" ["ucell"]="shigoto/ucell/vpn" ["ums"]="shigoto/ums/vpn" ["telekomsrbija"]="shigoto/telekomsrbija/vpn" )
+
+function get_from_pass () {
+ pass show "$1" | grep "^$2:" | sed -r "s|^$2: ||g"
+}
+
+[[ $# < 1 ]] && echo -e "Please specify a VPN connection profile.\nOptions:\n$( for conn in "${!connections[@]}"; do echo "$conn"; done)" && exit -1
+
+passentry="${connections["$1"]}"
+[[ -z $passentry ]] && echo -e "ERROR. VPN CONNECTION PROFILE NOT FOUND\nOptions:\n$( for conn in "${!connections[@]}"; do echo "$conn"; done)" && exit -2
+CONN_NAME="$( get_from_pass "$passentry" "name" )"
+
+if [[ "$( nmcli c s | grep "$CONN_NAME" | sed -r "s|.*\s+([^ ]+)\s+|\1|g" )" != "--" ]]; then
+ echo "ALREADY CONNECTED. DISCONNECTING..."
+ nmcli con down "$CONN_NAME"
+else
+ CONN_GATEWAY="$( get_from_pass "$passentry" "gateway" )"
+ CONN_CERTIFICATE="$( get_from_pass "$passentry" "certificate" )"
+ CONN_USERAGENT="$( get_from_pass "$passentry" "useragent" )"
+
+ [[ -n $CONN_CERTIFICATE ]] && CERTFLAG="--servercert $CONN_CERTIFICATE" || CERTFLAG=""
+ [[ -n $CONN_USERAGENT ]] && USERAGENT="--useragent $CONN_USERAGENT" || USERAGENT=""
+
+ CONN_USERNAME="$( get_from_pass "$passentry" "username" )"
+ CONN_PASSWORD="$( pass $passentry | head -n1 )"
+ echo "CONNECTING... "
+
+ if get_from_pass "$passentry" "OTP" | grep -q "yes"; then
+ eval ` { echo "$CONN_PASSWORD"; read OTP; echo "$OTP"; } | openconnect $USERAGENT -u "$CONN_USERNAME" --passwd-on-stdin $CERTFLAG --authenticate $CONN_GATEWAY `
+ # eval ` echo "$CONN_PASSWORD" | cat - /dev/tty | openconnect $USERAGENT -u "$CONN_USERNAME" --passwd-on-stdin $CERTFLAG --authenticate $CONN_GATEWAY `
+ else
+ eval ` echo "$CONN_PASSWORD" | openconnect $USERAGENT -u "$CONN_USERNAME" --passwd-on-stdin $CERTFLAG --authenticate $CONN_GATEWAY `
+ fi
+
+ if [ -z "$COOKIE" ]; then
+ echo "ERROR: NO COOKIE"
+ exit 1
+ else
+ nmcli con up "$CONN_NAME" passwd-file /proc/self/fd/5 5< <( printf "%s\n%s\n%s\n%s" "vpn.secrets.cookie:$COOKIE" "vpn.secrets.gwcert:$FINGERPRINT" "vpn.secrets.gateway:$CONN_GATEWAY" "vpn.secrets.resolve:$RESOLVE" )
+ fi
+fi
diff --git a/scriptlets/wine_jp_config.sh b/scriptlets/wine_jp_config.sh
new file mode 100644
index 0000000..ed48992
--- /dev/null
+++ b/scriptlets/wine_jp_config.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+export LOCPATH=$HOME/.wine/locale-ja/
+mkdir -p $LOCPATH
+localedef -f EUC-JP -i ja_JP $LOCPATH/ja_JP.EUC-JP
+localedef -c -f SHIFT_JIS -i ja_JP $LOCPATH/ja_JP.SJIS
+
+## You get a warning that SHIFT_JIS is not ASCII compatible, that is normal. With that setup you can now run apps using
+# env LOCPATH=$HOME/.wine/locale-ja/ LANG=ja_JP.SJIS wine prog.exe
+
+## or if the app is using EUC-JP (less common afaik)
+# env LOCPATH=$HOME/.wine/locale-ja/ LANG=ja_JP.EUC-JP wine prog.exe
+
diff --git a/scriptlets/wine_jp_run.sh b/scriptlets/wine_jp_run.sh
new file mode 100755
index 0000000..7bf14e4
--- /dev/null
+++ b/scriptlets/wine_jp_run.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+[[ "$1" =~ .*.exe$ ]] || { echo "ERROR"; exit -1; }
+
+env LOCPATH=$HOME/.wine/locale-ja/ LANG=ja_JP.SJIS wine "$1"
diff --git a/shell-rss-torrent b/shell-rss-torrent
new file mode 100755
index 0000000..16d416c
--- /dev/null
+++ b/shell-rss-torrent
@@ -0,0 +1,374 @@
+#!/bin/sh
+
+echo "
+------------------------
+ SHELL RSS TORRENT v2.5
+ Script by Rafostar
+------------------------
+"
+
+INDENT1=" "
+INDENT2=" "
+UPPER="ABCDEFGHIJKLMNOPQRSTUVWXYZ/ĄĆĘŁŃÓŚŹŻ"
+LOWER="abcdefghijklmnopqrstuvwxyz/ąćęłńóśźż"
+UA="Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/72.0"
+
+parse_config() {
+ WatchDir=$(echo "$ConfigData" | xmllint --xpath "string(//config/watchdir[1]/text())" - 2> /dev/null)
+ if [ "$?" -ne 0 ]
+ then
+ echo "Error: invalid config file"
+ exit 1
+ fi
+
+ Downloader=$(xmllint_from_config "string(//config/downloader[1]/text())")
+ if [ ! -z "$Downloader" ]
+ then
+ echo "Using <downloader> tag command"
+ DwnPrint=$(xmllint_from_config "count(//config/downloader[1][@print='1'])")
+
+ if [ -z "$WatchDir" ]
+ then
+ echo "Downloading without <watchdir> tag"
+ fi
+ else
+ if [ ! -z "$WatchDir" ]
+ then
+ echo "Torrents will be downloaded to: $WatchDir"
+ else
+ echo "Error: no <watchdir> in config file"
+ exit 1
+ fi
+ fi
+
+ HistoryPath=$(xmllint_from_config "string(//config/history[1]/text())")
+ if [ ! -z "$HistoryPath" ]
+ then
+ echo "Using download history file: $HistoryPath"
+
+ if [ -f "$HistoryPath" ]
+ then
+ HistoryData=$(cat "$HistoryPath")
+ fi
+ fi
+
+ TimeMax=$(xmllint_from_config "string(//config/time-max[1]/text())")
+ if [ ! -z "$TimeMax" ]
+ then
+ echo "Downloading releases within seconds: $TimeMax"
+ fi
+
+ FeedsCount=$(xmllint_from_config "count(//config/feed)")
+ echo "Total feeds in config: $FeedsCount"
+ echo ""
+
+ feed=1
+ while [ $feed -le $FeedsCount ]
+ do
+ FeedUrl=$(xmllint_from_config "string(//config/feed[$feed]/@url)")
+ if [ -z "$FeedUrl" ]
+ then
+ echo "Error: feed $feed has no url"
+ else
+ FeedUser=$(xmllint_from_config "string(//config/feed[$feed]/@user)")
+ if [ ! -z "$FeedUser" ]
+ then
+ FeedUser="--user=$FeedUser"
+ fi
+
+ FeedPass=$(xmllint_from_config "string(//config/feed[$feed]/@pass)")
+ if [ ! -z "$FeedPass" ]
+ then
+ FeedPass="--password=$FeedPass"
+ fi
+
+ FeedData=$(wget "$FeedUser" "$FeedPass" -U "$UA" -O - "$FeedUrl" 2> /dev/null)
+ if [ -z "$FeedData" ]
+ then
+ FeedData=$(wget "$FeedUser" "$FeedPass" -O - "$FeedUrl" 2> /dev/null)
+ fi
+
+ if [ -z "$FeedData" ]
+ then
+ echo "Error: feed $feed has no data"
+ else
+ echo "feed $feed: $FeedUrl"
+
+ for QueryFn in "starts-with" "contains"
+ do
+ find_in_feed $feed
+ done
+
+ find_in_feed_multi $feed
+ fi
+ fi
+
+ feed=$(( feed + 1 ))
+ done
+}
+
+find_in_feed() {
+ QueryPath="//config/feed[$1]/$QueryFn"
+ QueryCount=$(xmllint_from_config "count($QueryPath)")
+
+ if [ "$QueryCount" -ge 1 ]
+ then
+ query=1
+ while [ $query -le $QueryCount ]
+ do
+ find_attributes_matches $query
+ TorrentCount=$(xmllint_from_feed "count($Matches/link)")
+ echo "$INDENT1 $QueryFn $query: $QueryText, found: $TorrentCount"
+
+ if [ "$TorrentCount" -ge 1 ]
+ then
+ get_torrents
+ fi
+
+ query=$(( query + 1 ))
+ done
+ fi
+}
+
+find_in_feed_multi() {
+ MultiPath="//config/feed[$1]/multi"
+ MultiCount=$(xmllint_from_config "count($MultiPath)")
+
+ if [ "$MultiCount" -ge 1 ]
+ then
+ multi=1
+ while [ $multi -le $MultiCount ]
+ do
+ MultiText=""
+ CustomFeed=""
+
+ for QueryFn in "starts-with" "contains"
+ do
+ QueryPath="$MultiPath[$multi]/$QueryFn"
+ QueryCount=$(xmllint_from_config "count($QueryPath)")
+
+ if [ "$QueryCount" -ge 1 ]
+ then
+ query=1
+ while [ $query -le $QueryCount ]
+ do
+ find_attributes_matches "$query"
+ CustomFeed=$(xmllint_from_feed "$Matches")
+
+ if [ ! -z "$MultiText" ]
+ then
+ MultiText="$MultiText | $QueryText"
+ else
+ MultiText="$QueryText"
+ fi
+
+ if [ ! -z "$CustomFeed" ]
+ then
+ CustomFeed="<rss>$CustomFeed</rss>"
+ else
+ CustomFeed=""
+ echo "$INDENT1 multi $multi: $MultiText, found: 0"
+ break
+ fi
+
+ query=$(( query + 1 ))
+ done
+ fi
+ done
+
+ if [ ! -z "$CustomFeed" ]
+ then
+ get_torrents_multi $multi
+ CustomFeed=""
+ fi
+
+ multi=$(( multi + 1 ))
+ done
+ fi
+}
+
+find_attributes_matches() {
+ QueryText=$(xmllint_from_config "string($QueryPath[$1]/text())")
+ IgnoreCase=$(xmllint_from_config "count($QueryPath[$1][@ignore-case='1'])")
+
+ if [ "$IgnoreCase" -eq 1 ]
+ then
+ ParsedQuery=$(xmllint_from_config "translate($QueryPath[$query]/text(), '$UPPER', '$LOWER')")
+ Matches="//item[title[$QueryFn(translate(., '$UPPER', '$LOWER'), '$ParsedQuery')]]"
+ else
+ Matches="//item[title[$QueryFn(., '$QueryText')]]"
+ fi
+}
+
+find_torrent_attr() {
+ ATTR=$(xmllint_from_feed "count($Matches[$torrent]/$1)")
+ if [ "$ATTR" -eq 1 ]
+ then
+ ATTR_TORRENT=$(xmllint_from_feed "count($Matches[$torrent]/$1[@type='application/x-bittorrent'])")
+ if [ "$ATTR_TORRENT" -eq 1 ]
+ then
+ ATTR_URL=$(xmllint_from_feed "string($Matches[$torrent]/$1/@url)")
+ if [ ! -z "$ATTR_URL" ]
+ then
+ echo "$ATTR_URL"
+ fi
+ fi
+ fi
+}
+
+get_torrents() {
+ torrent=1
+ while [ $torrent -le $TorrentCount ]
+ do
+ TorrentLink=$(find_torrent_attr "enclosure")
+
+ if [ -z "$TorrentLink" ]
+ then
+ TorrentLink=$(xmllint_from_feed "string($Matches[$torrent]/link/text())")
+ fi
+
+ if [ ! -z "$TorrentLink" ]
+ then
+ if [ ! -z "$HistoryData" ]
+ then
+ case "$HistoryData" in
+ *"$TorrentLink"*) echo "$INDENT2 torrent $torrent in download history" ;;
+ *) try_download ;;
+ esac
+ else
+ try_download
+ fi
+ else
+ echo "$INDENT2 torrent $torrent without a link"
+ fi
+
+ torrent=$(( torrent + 1 ))
+ done
+}
+
+get_torrents_multi() {
+ TorrentCount=$(xmllint_from_feed "count($Matches/link)")
+ echo "$INDENT1 multi $1: $MultiText, found: $TorrentCount"
+
+ if [ "$TorrentCount" -ge 1 ]
+ then
+ get_torrents
+ fi
+}
+
+xmllint_from_config() {
+ RESULT=$(echo "$ConfigData" | xmllint --xpath "$1" - 2> /dev/null)
+ echo "$RESULT"
+}
+
+xmllint_from_feed() {
+ if [ ! -z "$CustomFeed" ]
+ then
+ XmlData="$CustomFeed"
+ else
+ XmlData="$FeedData"
+ fi
+
+ RESULT=$(echo "$XmlData" | xmllint --format --xpath "$1" - 2> /dev/null)
+ echo "$RESULT"
+}
+
+write_to_history() {
+ if [ ! -z "$HistoryPath" ]
+ then
+ echo "$1" >> "$HistoryPath"
+ fi
+
+ HistoryData=$(echo -e "$HistoryData\n$1")
+}
+
+try_download() {
+ if [ ! -z "$TimeMax" ]
+ then
+ check_date
+ else
+ download_torrent "$TorrentLink"
+ fi
+}
+
+check_date() {
+ DateCount=$(xmllint_from_feed "count($Matches[$torrent]/pubDate)")
+ if [ "$DateCount" -ge 1 ]
+ then
+ PubDate=$(xmllint_from_feed "string($Matches[$torrent]/pubDate/text())")
+ if [ ! -z "$PubDate" ]
+ then
+ TorrentEpoch=$(date -d "$PubDate" +%s)
+ CurrentEpoch=$(date +%s)
+ Elapsed=$((CurrentEpoch - TorrentEpoch))
+ if [ "$Elapsed" -le $TimeMax ]
+ then
+ download_torrent "$TorrentLink"
+ else
+ echo "$INDENT2 torrent $torrent is too old"
+ fi
+ fi
+ else
+ download_torrent "$TorrentLink"
+ fi
+}
+
+download_torrent() {
+ echo "$INDENT2 torrent $torrent: $1"
+
+ if [ ! -z "$Downloader" ]
+ then
+ Title=$(xmllint_from_feed "string($Matches[$torrent]/title/text())")
+
+ if [ "$DwnPrint" -eq 1 ]
+ then
+ eval "$Downloader "$1""
+ else
+ eval "$Downloader "$1"" > /dev/null 2> /dev/null
+ fi
+
+ if [ $? -eq 0 ]
+ then
+ write_to_history "$1"
+ else
+ echo "$INDENT1 $INDENT2 download failed"
+ fi
+ else
+ wget -q --content-disposition -U "$UA" -P "$WatchDir" "$1" 2> /dev/null
+ if [ $? -ne 0 ]
+ then
+ wget -P "$WatchDir" "$1" > /dev/null 2> /dev/null
+ if [ $? -eq 0 ]
+ then
+ write_to_history "$1"
+ else
+ echo "$INDENT1 $INDENT2 download failed"
+ fi
+ else
+ write_to_history "$1"
+ fi
+ fi
+}
+
+ConfigPath=$1
+if [ -z "$ConfigPath" ]
+then
+ ME=$(basename "$0")
+ echo "Usage: $ME <config_path>"
+else
+ if [ -f "$ConfigPath" ]
+ then
+ XmlInstalled=$(xmllint --version > /dev/null 2> /dev/null)
+ if [ $? -eq 0 ]
+ then
+ ConfigData=$(cat "$ConfigPath")
+ parse_config
+ else
+ echo "Error: xmllint is not installed"
+ exit 1
+ fi
+ else
+ echo "Error: file $ConfigPath does not exist"
+ exit 1
+ fi
+fi
diff --git a/sioyek_db_gen.sh b/sioyek_db_gen.sh
new file mode 100755
index 0000000..05a7b69
--- /dev/null
+++ b/sioyek_db_gen.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+
+PDFs="$HOME/PDFs"
+TTRPGs="$HOME/Downloads/TTRPGs"
+SIOYEK="$HOME/.local/share/sioyek"
+LOCALDB="$SIOYEK/local.db"
+SHAREDDB="$SIOYEK/shared.db"
+
+#backup just in case
+[[ -f "$LOCALDB" ]] && cp "$LOCALDB" "$LOCALDB.backup"
+[[ -f "$SHAREDDB" ]] && cp "$SHAREDDB" "$SHAREDDB.backup"
+
+echo "Compiling PDF list..."
+mapfile -t FILES < <( ( find $PDFs -type f -iregex "^.*\.pdf$" -printf "$PDFs/%P\n"; find $TTRPGs -type f -iregex "^.*\.pdf$" -printf "$TTRPGs/%P\n" ) | cat | sort --version-sort )
+
+# save against collision errors
+echo "Calculating file hashes..."
+
+i=0
+mapfile -t HASHES < <( for FILE in "${FILES[@]}"; do md5sum "$FILE" | cut -d' ' -f1 ; done )
+
+
+
+echo "Creating database..."
+sqlite3 "$LOCALDB" "drop table if exists document_hash;"
+sqlite3 "$LOCALDB" "create table if not exists document_hash (id INTEGER PRIMARY KEY AUTOINCREMENT,path TEXT,hash TEXT,UNIQUE(path, hash));"
+
+sqlite3 "$SHAREDDB" "create table if not exists bookmarks (id INTEGER PRIMARY KEY AUTOINCREMENT,document_path TEXT,desc TEXT,creation_time timestamp,modification_time timestamp,uuid TEXT,font_size integer DEFAULT -1,color_red real DEFAULT 0,color_green real DEFAULT 0,color_blue real DEFAULT 0,font_face TEXT,begin_x real DEFAULT -1,begin_y real DEFAULT -1,end_x real DEFAULT -1,end_y real DEFAULT -1,offset_y real);"
+sqlite3 "$SHAREDDB" "create table if not exists highlights (id INTEGER PRIMARY KEY AUTOINCREMENT,document_path TEXT,desc TEXT,text_annot TEXT,type char,creation_time timestamp,modification_time timestamp,uuid TEXT,begin_x real,begin_y real,end_x real,end_y real);"
+sqlite3 "$SHAREDDB" "create table if not exists links (id INTEGER PRIMARY KEY AUTOINCREMENT,creation_time timestamp,modification_time timestamp,uuid TEXT,src_document TEXT,dst_document TEXT,src_offset_y REAL,src_offset_x REAL,dst_offset_x REAL,dst_offset_y REAL,dst_zoom_level REAL);"
+sqlite3 "$SHAREDDB" "create table if not exists marks (id INTEGER PRIMARY KEY AUTOINCREMENT,document_path TEXT,symbol CHAR,offset_x real,offset_y real,zoom_level real,creation_time timestamp,modification_time timestamp,uuid TEXT,UNIQUE(document_path, symbol));"
+
+sqlite3 "$SHAREDDB" "drop table if exists opened_books;"
+sqlite3 "$SHAREDDB" "create table opened_books (id INTEGER PRIMARY KEY AUTOINCREMENT,path TEXT UNIQUE,document_name TEXT,zoom_level REAL,offset_x REAL,offset_y REAL,last_access_time TEXT);"
+
+i=0
+for FILE in "${FILES[@]}"; do
+ FILE=$( sed -r "s|'|''|g" <<< "$FILE" )
+ DATE=$( shuf -n1 -i$(date -d '1987-01-01' '+%s')-$(date '+%s') | xargs -I{} date -d '@{}' '+%Y-%m-%d %H:%M:%S' )
+ echo $FILE
+ sqlite3 "$LOCALDB" "insert into document_hash (path, hash) values ('$FILE', '${HASHES[i]}');"
+ sqlite3 "$SHAREDDB" "insert into opened_books (path, document_name, zoom_level, offset_x, offset_y, last_access_time) values ('${HASHES[i]}', '$FILE', 1.5, 0, 300, '$DATE');"
+ ((i++))
+done
+
+echo "Done."
diff --git a/tgscripts/foundrymacros-thirteenth.txt b/tgscripts/foundrymacros-thirteenth.txt
new file mode 100644
index 0000000..840b00e
--- /dev/null
+++ b/tgscripts/foundrymacros-thirteenth.txt
@@ -0,0 +1,387 @@
+event = "onclick";
+itemId = "CQVSSTo5BURaOi7L";
+charName = "Hisui";
+item = game.actors.getName(charName).items.get(itemId);
+item.roll();
+
+----------------------------------------------------------------------------------------------------
+
+const his = game.actors.getName("Hisui");
+his.update({'system.resources.spendable.custom1.current' : 1});
+his.system.resources.spendable.custom1.current = 1;
+game.macros.getName('Stance-AC-Change').execute()
+
+*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice
+
+----------------------------------------------------------------------------------------------------
+
+main()
+
+async function main() {
+const his2 = game.actors.getName("Hisui");
+console.log(his2.getRollData());
+const koh2 = game.actors.getName("Kohaku");
+console.log(koh2.getRollData());
+}
+
+
+----------------------------------------------------------------------------------------------------
+
+main()
+
+async function main(){
+
+const his = game.actors.getName("Hisui");
+var stance = his.data.data.resources.spendable.custom1.current;
+his.update({ 'system.attributes.ac.base': 11 + stance });
+}
+
+----------------------------------------------------------------------------------------------------
+
+The monk has failed to perform an attack from one of the three available stances on their turn, making their AC bonus reset to 0.
+
+const his = game.actors.getName("Hisui");
+his.update({'system.resources.spendable.custom1.current' : 0});
+his.system.resources.spendable.custom1.current = 0;
+game.macros.getName('Stance-AC-Change').execute()
+
+----------------------------------------------------------------------------------------------------
+
+let applyWeaponChanges = false;
+let applyFistChanges = false;
+const his = game.actors.getName("Hisui");
+
+let style = '<h2 style="font-family:mason-serif; background-image:linear-gradient(45deg, #25003c, rgba(220, 159, 241, 0.6509803922)); color:#fff; padding-left: 4px; text-shadow: 1px 1px 2px #000">'
+
+let message = style + 'Weapon Select</h2>\n<p style="font-family:warnock-pro;">Hisui is using ';
+
+const myDialogOptions = {
+ width: 535,
+ height: 115
+};
+
+new Dialog({
+ title : `Improvised Weapon Select`,
+ content : `<label>Weapon type: </label>
+ <select name="attackmod" id="attackmod">
+ <option value="2h">Two-Handed</option>
+ <option value="1h">One-handed</option>
+ </select>
+ <select name="range" id="range">
+ <option value="melee">Melee</option>
+ <option value="ranged">Ranged</option>
+ </select>
+ <label> Weapon damage: </label>
+ <select name="dice" id="dice">
+ <option value="d4">d4</option>
+ <option value="d6">d6</option>
+ <option value="d8">d8</option>
+ <option value="d10">d10</option>
+ <option value="d12">d12</option>
+ </select>
+ <input type="checkbox" id="thrown" name="thrown">
+ <label for="thrown">Thrown</label>`,
+ buttons : {
+ weapon : {
+ icon : "<i class='fas fa-check'></i>",
+ label : `Equip Weapon`,
+ callback : () => { applyWeaponChanges = true; applyFistChanges = false; }
+ },
+ fists : {
+ icon : "<i class='fas fa-skull-crossbones'></i>",
+ label : `Equip Hands`,
+ callback : () => { applyWeaponChanges = false; applyFistChanges = true; }
+ },
+ cancel : {
+ icon : "<i class='fas fa-times'></i>",
+ label : `Cancel`,
+ callback : () => { applyWeaponChanges = false; applyFistChanges = false; }
+ }
+ },
+ default : "weapon",
+ close : html => {
+ if (applyWeaponChanges || applyFistChanges) {
+ if(applyWeaponChanges) {
+ let die = ( html.find('[name="dice"]')[0].value || "d8" );
+ let hand = ( html.find('[name="attackmod"]')[0].value || "1h" );
+ let range = ( html.find('[name="range"]')[0].value || "melee" );
+ let thrown = ( html.find('[name="thrown"]')[0].checked );
+ let mod = ( hand == "2h" && range == "melee" ) ? (his.data.data.abilities.str.mod - his.data.data.abilities.dex.mod) : 0;
+
+ if ( range == "melee" ){
+ his.system.attributes.weapon.melee.dice = die;
+ his.update({'system.attributes.weapon.melee.dice' : die });
+ }
+ if ( range == "ranged" || thrown == true ){
+ his.system.attributes.weapon.ranged.dice = die;
+ his.update({'system.attributes.weapon.ranged.dice' : die });
+ }
+
+ his.system.attributes.attackMod.value = mod;
+
+ his.update({'system.attributes.weapon.melee.dualwield' : (hand == "1h" && range == "melee") ? 'true' : 'false' });
+
+ message += 'a ' + ( hand == "1h" ? '<strong>one-handed</strong> ' : '<strong>two-handed</strong> ') + ( range == "melee" ? '<strong>melee</strong> ' : '<strong>ranged</strong> ' ) + ( thrown == true ? '<strong>(thrown) </strong> ' : '' ) + 'weapon with a <strong>' + die + '</strong> damage die.</p>';
+
+ if ( range == "ranged" ) applyWeaponChanges = false
+
+ } else {
+ his.system.attributes.attackMod.value = 0;
+ his.update({'system.attributes.weapon.melee.dualwield' : 'true' });
+
+ message += 'her bare hands.</p>'
+ }
+
+ his.update({'system.resources.spendable.custom2.current' : (applyWeaponChanges == true) ? 1 : 0});
+
+ ChatMessage.create({
+ user: game.user._id,
+ speaker: ChatMessage.getSpeaker({token: actor}),
+ content: message
+ });
+
+
+ }
+ }
+ }, myDialogOptions).render(true);
+
+----------------------------------------------------------------------------------------------------
+
+let applyBoost = false;
+let applyUnboost = false;
+const his = game.actors.getName("Hisui");
+
+let style = '<h2 style="font-family:mason-serif; background-image:linear-gradient(45deg, #25003c, rgba(220, 159, 241, 0.6509803922)); color:#fff; padding-left: 4px; text-shadow: 1px 1px 2px #000">'
+
+let message = style + 'Blue-Eye Boost</h2>\n<p style="font-family:warnock-pro;">Hisui\'s Blue-Eye Boost has ';
+
+const myDialogOptions = {
+ width: 435,
+ height: 115
+};
+
+new Dialog({
+ title : `Blue-Eye Boost`,
+ content : `<label>Boost?</label>`,
+ buttons : {
+ boost : {
+ icon : "<i class='fas fa-check'></i>",
+ label : `Boost`,
+ callback : () => { applyBoost = true; applyUnboost = false; }
+ },
+ unboost : {
+ icon : "<i class='fas fa-skull-crossbones'></i>",
+ label : `Unboost`,
+ callback : () => { applyBoost = false; applyUnboost = true; }
+ },
+ cancel : {
+ icon : "<i class='fas fa-times'></i>",
+ label : `Cancel`,
+ callback : () => { applyBoost = false; applyUnboost = false; }
+ }
+ },
+ default : "boost",
+ close : html => {
+
+ rangeddie = his.system.attributes.weapon.ranged.dice;
+ jabdie = his.system.attributes.weapon.jab.dice;
+ punchdie = his.system.attributes.weapon.punch.dice;
+ kickdie = his.system.attributes.weapon.kick.dice;
+
+ rangedvalue = parseInt(rangeddie.slice(1));
+ jabvalue = parseInt(jabdie.slice(1));
+ punchvalue = parseInt(punchdie.slice(1));
+ kickvalue = parseInt(kickdie.slice(1));
+
+ if (applyBoost || applyUnboost) {
+ if(applyBoost) {
+
+ his.update({'system.resources.spendable.custom3.current' : rangedvalue});
+
+ rangedvalue += 2;
+ jabvalue += 2;
+ punchvalue += 2;
+ kickvalue += 2;
+
+ rangeddie = 'd' + rangedvalue;
+ jabdie = 'd' + jabvalue;
+ punchdie = 'd' + punchvalue;
+ kickdie = 'd' + kickvalue;
+
+ his.system.attributes.weapon.ranged.dice = rangeddie;
+ his.update({'system.attributes.weapon.ranged.dice' : rangeddie });
+
+ his.system.attributes.weapon.jab.dice = jabdie;
+ his.update({'system.attributes.weapon.jab.dice' : jabdie });
+
+ his.system.attributes.weapon.punch.dice = punchdie;
+ his.update({'system.attributes.weapon.punch.dice' : punchdie });
+
+ his.system.attributes.weapon.kick.dice = kickdie;
+ his.update({'system.attributes.weapon.kick.dice' : kickdie });
+
+ message += 'been activated.</p>';
+
+ } else {
+
+ rangedvalue = his.system.resources.spendable.custom3.current == 0 ? rangedvalue : his.system.resources.spendable.custom3.current;
+ jabvalue = 6;
+ punchvalue = 8;
+ kickvalue = 10;
+
+ his.update({'system.resources.spendable.custom3.current' : 0});
+
+ rangeddie = 'd' + rangedvalue;
+ jabdie = 'd' + jabvalue;
+ punchdie = 'd' + punchvalue;
+ kickdie = 'd' + kickvalue;
+
+ his.system.attributes.weapon.ranged.dice = rangeddie;
+ his.update({'system.attributes.weapon.ranged.dice' : rangeddie });
+
+ his.system.attributes.weapon.jab.dice = jabdie;
+ his.update({'system.attributes.weapon.jab.dice' : jabdie });
+
+ his.system.attributes.weapon.punch.dice = punchdie;
+ his.update({'system.attributes.weapon.punch.dice' : punchdie });
+
+ his.system.attributes.weapon.kick.dice = kickdie;
+ his.update({'system.attributes.weapon.kick.dice' : kickdie });
+
+ message += 'expired.</p>';
+ }
+
+ ChatMessage.create({
+ user: game.user._id,
+ speaker: ChatMessage.getSpeaker({token: actor}),
+ content: message
+ });
+
+ }
+ }
+ }, myDialogOptions).render(true);
+
+----------------------------------------------------------------------------------------------------
+
+Talent
+Flurry (7 Deadly Secrets)
+Monk
+Melee
+Once per round, when the escalation die is 3+.
+One enemy
+
+[[d20+@dex.mod+@std+@atk.m.bonus]] vs. AC
+
+[[@wpn.j.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+@atk.m.bonus]] damage.
+
+If you use Flurry in a battle, you can’t use any other Deadly Secrets talents that battle. 
+
+Ki Power: (A Thousand Palms): You must be engaged with 2 or more enemies to use this power. After making a Flurry attack, you can spend 1 point of ki to make another Flurry attack against a target you have not already attacked with Flurry this turn.
+
+----------------------------------------------------------------------------------------------------
+
+Opening
+Badger (Rabid Badger)
+Monk
+1
+Melee
+One enemy
+
+[[d20+@dex.mod+@std+@atk.m.bonus - 2]] vs. AC
+
+[[@wpn.j.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+(@lvl)d6+@atk.m.bonus]] damage.
+
+[[(@tier)d6]] damage.
+
+Reduce the attack penalty of all attacks in this form to -2.
+
+-------------------------
+
+Flow
+Badger Badger (Rabid Badger)
+Monk
+2
+Melee
+Two enemies
+
+[[d20+@dex.mod+@std+@atk.m.bonus - 2]] vs. AC
+
+[[@wpn.p.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+@atk.m.bonus]] damage.
+
+[[(@tier)d6]] damage.
+
+Reduce the attack penalty of all attacks in this form to -2.
+
+-------------------------
+
+Finishing
+Badger Badger Badger (Rabid Badger)
+Monk
+3
+Melee
+3x One enemy
+Make two follow-up attacks against the same target.
+
+[[d20+@dex.mod+@std+@atk.m.bonus - 2]] vs. AC
+
+[[@wpn.k.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+@atk.m.bonus]] damage.
+
+[[(@tier)d6]] damage.
+
+If follow-up #1 hits: [[@wpn.j.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@atk.m.bonus]] damage.
+
+If follow-up #2 hits: [[@wpn.j.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@atk.m.bonus]] damage.
+
+Reduce the attack penalty of all attacks in this form to -2.
+
+----------------------------------------------------------------------------------------------------
+
+Opening
+Embrace the Shadow (Shadow Fist)
+Monk
+1
+Melee
+One enemy
+
+[[d20+@dex.mod+@std+@atk.m.bonus]] vs. MD
+
+[[@wpn.j.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+@atk.m.bonus]] damage.
+
+[[@lvl]] damage. You can spend a ki point to gain the cycle bonus.
+
+Cycle bonus: Gain a bonus to disengage checks equal to your Wisdom modifier ([[@wis.mod]]).
+
+-------------------------
+
+Flow
+Shadow Curtain (Shadow Fist)
+Monk
+2
+Melee
+One enemy
+
+[[d20+@dex.mod+@std+@atk.m.bonus]] vs. MD
+
+[[@wpn.p.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+@atk.m.bonus]] damage, and if the next attack against you is a natural odd roll, change the target to this enemy.
+
+[[@lvl]] damage.
+
+-------------------------
+
+Finishing
+Shadow Dance (Shadow Fist)
+Monk
+3
+Melee
+One enemy
+
+[[d20+@dex.mod+@std+@atk.m.bonus]] vs. MD
+
+[[@wpn.k.dice*(1-@rsc.weapontracker)+(@rsc.weapontracker)*@wpn.m.dice+@str.dmg+@atk.m.bonus]] damage, and you teleport to a nearby location you can see.
+
+You can spend a ki point to reroll the attack, and add your Wisdom modifier ([[@wis.mod]]) as a bonus to the attack.
+
+----------------------------------------------------------------------------------------------------
+
+
+
diff --git a/tgscripts/fvtt-Macro-infodump.json b/tgscripts/fvtt-Macro-infodump.json
new file mode 100644
index 0000000..bbb3016
--- /dev/null
+++ b/tgscripts/fvtt-Macro-infodump.json
@@ -0,0 +1,24 @@
+{
+ "name": "Infodump",
+ "type": "script",
+ "scope": "global",
+ "author": "3r8Uo5wkrb6EqEZc",
+ "img": "icons/svg/dice-target.svg",
+ "command": "main()\n\nasync function main() {\nconst his2 = game.actors.getName(\"Hisui\");\nconsole.log(his2.getRollData());\nconst koh2 = game.actors.getName(\"Kohaku\");\nconsole.log(koh2.getRollData());\n}",
+ "flags": {
+ "exportSource": {
+ "world": "valoran",
+ "system": "archmage",
+ "coreVersion": "10.291",
+ "systemVersion": "1.23.1"
+ }
+ },
+ "_stats": {
+ "systemId": "archmage",
+ "systemVersion": "1.23.1",
+ "coreVersion": "10.290",
+ "createdTime": 1669971762155,
+ "modifiedTime": 1669981028855,
+ "lastModifiedBy": "3r8Uo5wkrb6EqEZc"
+ }
+} \ No newline at end of file
diff --git a/tgscripts/fvtt-Macro-stance-ac-change.json b/tgscripts/fvtt-Macro-stance-ac-change.json
new file mode 100644
index 0000000..c2c833d
--- /dev/null
+++ b/tgscripts/fvtt-Macro-stance-ac-change.json
@@ -0,0 +1,24 @@
+{
+ "name": "Stance-AC-Change",
+ "type": "script",
+ "scope": "global",
+ "author": "3r8Uo5wkrb6EqEZc",
+ "img": "icons/svg/dice-target.svg",
+ "command": "main()\n\nasync function main(){\n\nconst his = game.actors.getName(\"Hisui\");\nvar stance = his.data.data.resources.spendable.custom1.current;\nhis.update({ 'system.attributes.ac.base': 11 + stance });\n}",
+ "flags": {
+ "exportSource": {
+ "world": "valoran",
+ "system": "archmage",
+ "coreVersion": "10.291",
+ "systemVersion": "1.23.1"
+ }
+ },
+ "_stats": {
+ "systemId": "archmage",
+ "systemVersion": "1.23.1",
+ "coreVersion": "10.290",
+ "createdTime": 1669976113729,
+ "modifiedTime": 1670077474135,
+ "lastModifiedBy": "3r8Uo5wkrb6EqEZc"
+ }
+} \ No newline at end of file
diff --git a/tgscripts/fvtt-Macro-weapon-select.json b/tgscripts/fvtt-Macro-weapon-select.json
new file mode 100644
index 0000000..4db9192
--- /dev/null
+++ b/tgscripts/fvtt-Macro-weapon-select.json
@@ -0,0 +1,24 @@
+{
+ "name": "Weapon-Select",
+ "type": "script",
+ "scope": "global",
+ "author": "3r8Uo5wkrb6EqEZc",
+ "img": "icons/svg/dice-target.svg",
+ "command": "let applyWeaponChanges = false;\nlet applyFistChanges = false;\nconst his = game.actors.getName(\"Hisui\");\n\nlet style = '<h2 style=\"font-family:mason-serif; background-image:linear-gradient(45deg, #25003c, rgba(220, 159, 241, 0.6509803922)); color:#fff; padding-left: 4px; text-shadow: 1px 1px 2px #000\">'\n\nlet message = style + 'Weapon Select</h2>\\n<p style=\"font-family:warnock-pro;\">Hisui is using ';\n\n\n\nnew Dialog({\n title : `Improvised Weapon Select`,\n content : `<label>Weapon type: </label>\n <select name=\"attackmod\" id=\"attackmod\">\n <option value=\"2h\">Two-Handed</option>\n <option value=\"1h\">One-handed</option>\n </select>\n <label> Weapon damage: </label>\n <select name=\"dice\" id=\"dice\">\n <option value=\"d4\">d4</option>\n <option value=\"d6\">d6</option>\n <option value=\"d8\">d8</option>\n <option value=\"d10\">d10</option>\n </select>`,\n buttons : {\n weapon : {\n icon : \"<i class='fas fa-check'></i>\",\n label : `Equip Weapon`,\n callback : () => { applyWeaponChanges = true; applyFistChanges = false; }\n },\n fists : {\n icon : \"<i class='fas fa-skull-crossbones'></i>\",\n label : `Equip Hands`,\n callback : () => { applyWeaponChanges = false; applyFistChanges = true; }\n },\n cancel : {\n icon : \"<i class='fas fa-times'></i>\",\n label : `Cancel`,\n callback : () => { applyWeaponChanges = false; applyFistChanges = false; }\n }\n },\n default : \"weapon\",\n close : html => {\n if (applyWeaponChanges || applyFistChanges) {\n if(applyWeaponChanges) {\n let die = ( html.find('[name=\"dice\"]')[0].value || \"d8\" );\n let hand = ( html.find('[name=\"attackmod\"]')[0].value || \"1h\" );\n let mod = ( hand == \"2h\" ) ? (his.data.data.abilities.str.mod - his.data.data.abilities.dex.mod) : 0; \n\n his.system.attributes.weapon.melee.dice = die;\n his.system.attributes.attackMod.value = mod;\n\n his.update({'system.attributes.weapon.melee.dualwield' : (hand == \"1h\") ? 'true' : 'false' });\n\n message += 'a '+ ( hand == \"1h\" ? '<strong>one-handed</strong> ' : '<strong>two-handed</strong> ') + 'weapon with a <strong>' + die + '</strong> damage die.</p>';\n\n \n } else {\n his.system.attributes.attackMod.value = 0;\n his.update({'system.attributes.weapon.melee.dualwield' : 'true' });\n \n message += 'her bare hands.</p>'\n }\n\n his.update({'system.resources.spendable.custom2.current' : (applyWeaponChanges == true) ? 1 : 0});\n\n ChatMessage.create({\n user: game.user._id,\n speaker: ChatMessage.getSpeaker({token: actor}),\n content: message\n });\n\n\n }\n }\n }).render(true);",
+ "flags": {
+ "exportSource": {
+ "world": "valoran",
+ "system": "archmage",
+ "coreVersion": "10.291",
+ "systemVersion": "1.23.1"
+ }
+ },
+ "_stats": {
+ "systemId": "archmage",
+ "systemVersion": "1.23.1",
+ "coreVersion": "10.291",
+ "createdTime": 1670059784200,
+ "modifiedTime": 1670152580815,
+ "lastModifiedBy": "3r8Uo5wkrb6EqEZc"
+ }
+} \ No newline at end of file
diff --git a/tgscripts/roll20macros-dnd.txt b/tgscripts/roll20macros-dnd.txt
new file mode 100644
index 0000000..6878168
--- /dev/null
+++ b/tgscripts/roll20macros-dnd.txt
@@ -0,0 +1,48 @@
+
+* ABILITY SCORES
+
+&{template:default} {{name= Ability Scores}} {{[[4d6kh3]][[4d6kh3]][[4d6kh3]][[4d6kh3]][[4d6kh3]][[4d6kh3]]}}
+
+
+
+* GM-WHISPERED DEATH SAVING THROW
+
+/w gm &{template:simple} {{rname=DEATH SAVE}} {{mod=0}} {{r1=[[1d20]]}} {{normal=1}}
+
+
+
+* BLESS (SPELL)
+
+&{template:simple} {{rname=Bless}} {{r1=[[1d4]]}} {{normal=1}} {{charname=+ Attack Roll/Saving Throw}}
+
+
+
+* EAR FOR DECEIT (ROGUE: INQUISITIVE)
+
+&{template:simple} {{rname=EAR FOR DECEIT}} {{mod=@{insight_bonus}}} {{r1=[[1d20 + @{insight_bonus}]]}} {{advantage=1}} {{r2=[[8 + @{insight_bonus}]]}} {{charname=The Great Detective Knows}}
+
+
+
+* ROGUE SNEAK ATTACK
+
+[[round(@{base_level}/2)]]d6
+
+
+
+* MONK-EXCLUSIVE STAT DUMP
+
+&{template:default} {{name=Monk-Specific Stat Dump}} {{Unarmored AC=[[10 + @{wisdom_mod}[WIS] + @{dexterity_mod}[DEX]]]}} {{Unarmored Movement=**+**[[10*ceil(floor(@{base_level} / 2) / 20) + 5*ceil(floor(@{base_level} / 6) / 20) + 5*ceil(floor(@{base_level} / 10) / 20) + 5*ceil(floor(@{base_level} / 14) / 20) + 5*ceil(floor(@{base_level} / 18) / 20)]] }} {{Martial Arts Die=**1d**[[2+2*round((@{base_level} + 1) / 6 + 0.5)]]
+}} {{Maximum Ki Points=[[@{base_level}[LEVEL]]]}} {{Ki Save DC=[[8 + @{wisdom_mod}[WIS] + @{pb}[PROF]]]}} {{Slow Fall Damage Soak=[[5 * @{base_level}[LEVEL]]]}}
+
+
+
+* MONK MARTIAL ARTS DIE
+
+1d[[2+2*round((@{base_level} + 1) / 6 + 0.5)]]
+
+
+
+* MONK DEFLECT MISSILE
+
+1d10+@{level} + DEX
+
diff --git a/tgscripts/roll20macros-l5r.txt b/tgscripts/roll20macros-l5r.txt
new file mode 100644
index 0000000..8c96d43
--- /dev/null
+++ b/tgscripts/roll20macros-l5r.txt
@@ -0,0 +1,3 @@
+GENERIC DICE ROLL
+
+&{template:l5r5th} {{name=Generic Dice Roll}} {{r0=[[?{How many Ring dice?|0|1|2|3|4|5|6|7|8|9|10}]]}} {{s0=[[?{How many Skill dice?|0|1|2|3|4|5|6|7|8|9|10}]]}} {{r1=[[1d6]]}} {{r2=[[1d6]]}} {{r3=[[1d6]]}} {{r4=[[1d6]]}} {{r5=[[1d6]]}} {{r6=[[1d6]]}} {{r7=[[1d6]]}} {{r8=[[1d6]]}} {{r9=[[1d6]]}} {{r10=[[1d6]]}} {{s1=[[1d12]]}} {{s2=[[1d12]]}} {{s3=[[1d12]]}} {{s4=[[1d12]]}} {{s5=[[1d12]]}} {{s6=[[1d12]]}} {{s7=[[1d12]]}} {{s8=[[1d12]]}} {{s9=[[1d12]]}} {{s10=[[1d12]]}} \ No newline at end of file
diff --git a/tgscripts/roll20macros-thirteenth.txt b/tgscripts/roll20macros-thirteenth.txt
new file mode 100644
index 0000000..c662c83
--- /dev/null
+++ b/tgscripts/roll20macros-thirteenth.txt
@@ -0,0 +1,427 @@
+TEMPLATE FUNCTIONS
+
+[[{{x,false-value-compared-to-A}>A}*(T-F) + F]]
+[[({0,floor(1-abs(x-A))}dl1)*(T-F) +F]]
+
+?{Modifiers|0} MODIFIERS GET SAVED WHEN THEY GET RECORDED ONCE, YOU CAN REUSE THEM BY INVOKING THE SAME ?{}
+
+CONFIGURE BASIC MELEE AND RANGED ATTACK IN COG, ALONG WITH EVERYTHING ELSE
+
+/w Kohaku &{template:default} {{name=Utility Menu}} {{Rule=[Two Weapon Fighting](!&#13;#Rule-Two-Weapon-Fighting)}} {{Racial=[Quick to Fight](!&#13;#Racial-Quick-to-Fight)}} {{Item=[Wooden Ring](!&#13;#Item-Wooden-Ring)}} {{Feat=[Bribery](!&#13;#Feat-Bribery)}} {{Feature=[Momentum](!&#13;#Feature-Momentum) [Sneak Attack](!&#13;#Feature-Sneak-Attack) [Trap Sense](!&#13;#Feature-Trap-Sense)}} {{Talent=[Babyface](!&#13;#Talent-Babyface) [Intrusion Expert](!&#13;#Talent-Intrusion-Expert) [Shadow Walk](!&#13;#Talent-Shadow-Walk)}}
+
+CUSTOM VALUE INSIDE THING
+
+icon-final1
+{{name=@{icon-name1}}} {{relationship=^{@{icon-type1}}}} {{iconlvl=[[@{icon1}]]}} {{iconrow=1}}
+
+icon-final2
+{{name=@{icon-name2}}} {{relationship=^{@{icon-type2}}}} {{iconlvl=[[@{icon2}]]}} {{iconrow=2}}
+
+icon-final3
+{{name=@{icon-name3}}} {{relationship=^{@{icon-type3}}}} {{iconlvl=[[@{icon3}]]}} {{iconrow=3}}
+
+icon-final4
+{{name=@{icon-name4}}} {{relationship=^{@{icon-type4}}}} {{iconlvl=[[@{icon4}]]}} {{iconrow=4}}
+
+THE ROLL
+&{template:icon} {{icon1=[[1d6cf5]]}} {{icon2=[[1d6cf5]]}} {{icon3=[[1d6cf5]]}} {{icon4=[[1d6cf5]]}} {{charname=@{Kohaku|character_name}}} @{Kohaku|icon-final1}
+COPY AND PASTE IN MACRO AS MANY TIMES AS YOU HAVE ICONS, CHANGING ONLY THE CHARACTER NAME AND X IN finalX (1-4)
+
+MWEAP-name
+[[({0,floor(1-abs(@{MWEAP-sel}-1))}dl1)*@{MWEAP-name1} + ({0,floor(1-abs(@{MWEAP-sel}-2))}dl1)*@{MWEAP-name2} + ({0,floor(1-abs(@{MWEAP-sel}-3))}dl1)*@{MWEAP-name3}]]
+
+MWEAP-mod
+[[({0,floor(1-abs(@{MWEAP-sel}-1))}dl1)*@{MWEAP-mod1} + ({0,floor(1-abs(@{MWEAP-sel}-2))}dl1)*@{MWEAP-mod2} + ({0,floor(1-abs(@{MWEAP-sel}-3))}dl1)*@{MWEAP-mod3}]]
+
+MWEAP
+[[({0,floor(1-abs(@{MWEAP-sel}-1))}dl1)*@{MWEAP1} + ({0,floor(1-abs(@{MWEAP-sel}-2))}dl1)*@{MWEAP2} + ({0,floor(1-abs(@{MWEAP-sel}-3))}dl1)*@{MWEAP3}]]
+
+RWEAP-name
+[[({0,floor(1-abs(@{RWEAP-sel}-1))}dl1)*@{RWEAP-name1} + ({0,floor(1-abs(@{RWEAP-sel}-2))}dl1)*@{RWEAP-name2} + ({0,floor(1-abs(@{RWEAP-sel}-3))}dl1)*@{RWEAP-name3}]]
+
+RWEAP-mod
+[[({0,floor(1-abs(@{RWEAP-sel}-1))}dl1)*@{RWEAP-mod1} + ({0,floor(1-abs(@{RWEAP-sel}-2))}dl1)*@{RWEAP-mod2} + ({0,floor(1-abs(@{RWEAP-sel}-3))}dl1)*@{RWEAP-mod3}]]
+
+RWEAP
+[[({0,floor(1-abs(@{RWEAP-sel}-1))}dl1)*@{RWEAP1} + ({0,floor(1-abs(@{RWEAP-sel}-2))}dl1)*@{RWEAP2} + ({0,floor(1-abs(@{RWEAP-sel}-3))}dl1)*@{RWEAP3}]]
+
+
+CUSTOM ATTACK MODIFIERS
+@{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+(+ 2[FEINT])
+@{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+
+
+CUSTOM DAMAGE MODIFIERS
+[[@{MBA-hit}]]
+[[@{MBA-hit} + @{CHA-mod}[CHA]*@{LVL-multiplier}[LVL]]]
+[[@{level}[LVL]]]
+[[@{CHA-mod}[CHA]*@{LVL-multiplier}[LVL]]]
+[[@{level}[LVL] + @{CHA-mod}[CHA]*@{LVL-multiplier}[LVL]]]
+
+
+
+-------------------------ROGUE SNEAK ATTACK-------------------------
+
+[[ ({0,floor(1-abs(@{level}-1))}dl1)*1d4 + {{@{level},0}>[[2]]}*1d6 + {{@{level},0}>[[4]]}*1d6 + {{@{level},0}>[[6]]}*1d6 + {{@{level},0}>[[8]]}*2d6 + {{@{level},0}>[[10]]}*2d6 ]]
+
+
+-------------------------SHADOW WALK-------------------------
+
+Name: Shadow Walk
+Type: At-Will
+Action: Move Action
+Range: Nearby
+Target: All nearby enemies, targeting the enemy among them with the highest Mental Defense
+Attack: Custom - @{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: MD
+
+Custom 1:
+Hit:
+- Remove yourself from play. At the start of your next turn, return anywhere nearby that you could have moved to normally during your turn, and deal double damage with your first rogue attack that turn.
+
+Custom 2:
+Miss:
+- No effect. You can’t attempt to shadow walk again until your next turn, but you still have your standard action this turn.
+
+Custom 3:
+Custom - (blank):
+- **Adventurer Feat:** On a miss, you can still use your move action normally (but still can’t shadow walk this turn).
+- **Champion Feat:** Twice per day, you can reroll the rogue attack that follows your successful use of shadow walk.
+- **Epic Feat:** Twice per day, you can reappear from your shadow walk in a nearby location you wouldn’t have been able to reach unimpeded physically, for instance, on the other side of a portcullis or door, or high up a wall.
+
+
+-------------------------EVASIVE STRIKE-------------------------
+
+Name: Evasive Strike
+Type: At-Will
+Action: Custom - Melee Attack
+Range: Engaged
+Target: One enemy
+Attack: Custom - @{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: AC
+
+Custom 1:
+Hit:
+- [[@{MBA-hit}]] damage, and you can pop free from the target.
+
+Custom 2:
+Miss:
+- [[@{level}[LVL]]] damage
+
+-------------------------PERFECT EXECUTION-------------------------
+
+Name: Perfect Execution
+Type: Daily
+Action: Custom - Melee Attack; Requires Momentum
+Range: Engaged
+
+Custom 1:
+Effect:
+- [[50]] damage
+- *9th level power:* [[75]] damage
+
+-------------------------WELL-TIMED STRIKE-------------------------
+
+Name: Well-Timed Strike
+Type: Encounter
+Action: Custom - Melee Attack
+Range: Engaged
+Attack: Custom - @{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: Custom - the lowest defense of the target
+
+Custom 1:
+Special:
+- The target is vulnerable to the attack.
+
+Custom 2:
+Hit:
+- [[@{MBA-hit}]] damage
+
+Custom 3:
+Miss:
+- Half damage
+
+
+-------------------------ROLL WITH IT-------------------------
+
+Name: Roll With It
+Type: At-Will
+Action: Custom - Interrupt Action; Requires Momentum
+
+Custom 1:
+Trigger:
+- A melee attack that targets AC hits you.
+
+Custom 2:
+Effect:
+- You take half damage from that attack.
+
+
+-------------------------CHEEKY DISTRACTION-------------------------
+
+Name: Cheeky Distraction
+Type: At-Will
+Action: Custom - Interrupt Action; Requires Momentum
+Range: Engaged
+Attack: Custom - @{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: MD
+
+Custom 1:
+Trigger:
+- An enemy you are engaged with makes an attack against you or an ally.
+
+Custom 2:
+Hit:
+- The enemy has disadvantage on the attack roll.
+- *Critical hit:* The attack fails and has no effect.
+- **Champion Feat:** If you hit, you can pop free from the target after it made the attack.
+
+Custom 3:
+Miss:
+- -
+
+-------------------------DARK SHROUD-------------------------
+
+Name: Dark Shroud
+Type: Encounter
+Action: Custom - Close-quarters attack; Quick action; Shadow
+Range: Nearby
+Target: One nearby enemy
+Attack: Custom - @{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: MD
+
+Custom 1:
+Hit:
+- The target has disadvantage on their next attack roll. If you make a melee or ranged weapon attack against the target this turn, you can use your Sneak Attack, even if it is not engaged with an ally.
+
+Custom 2:
+Miss:
+- -
+
+-------------------------NOT TODAY, DEATH-------------------------
+
+Name: Not Today, Death
+Type: Daily / Recharge
+Action: Custom - Interrupt Action; Requires Momentum; Shadow
+
+Custom 1:
+Trigger:
+- An attack reduces you to zero hit points or below.
+
+Custom 2:
+Effect:
+- You take no damage or other effects from the attack. You can spend your momentum to disappear and reappear anywhere nearby.
+
+Custom 3:
+Custom - (blank):
+- **Epic Feat:** The power is now Recharge 16+ after battle.
+
+-------------------------TRICKSTER'S CUT-------------------------
+
+Name: Trickster's Cut
+Type: At-Will
+Action: Custom - Melee Attack
+Range: Engaged
+Attack: Custom - @{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: MD
+
+Custom 1:
+Hit:
+- [[@{MBA-hit}]] damage
+
+Custom 2:
+Miss:
+- [[@{level}[LVL]]] damage / [[@{level}[LVL] + @{CHA-mod}[CHA]*@{LVL-multiplier}[LVL]]] damage
+- **Adventurer Feat:** Add your Charisma modifier to miss damage.
+
+-------------------------TUMBLING STRIKE-------------------------
+
+Name: Tumbling Strike
+Type: At-Will
+Action: Custom - Melee Attack
+Range: Engaged
+Attack: Custom - @{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: AC
+
+Custom 1:
+Custom - Always:
+- You gain a +5 bonus to all disengage checks you attempt this turn. You can also move to engage an enemy, make this attack against it, and then use a quick action to attempt to disengage from it (the quick action disengage lets you move again if you succeed).
+
+Custom 2:
+Hit:
+- [[@{MBA-hit}]] damage
+
+Custom 3:
+Miss:
+- [[@{level}[LVL]]] damage
+
+-------------------------SLICK FEINT-------------------------
+
+TWO-PARTER
+
+Name: Slick Feint - **Act I**
+Type: At-Will
+Action: Custom - Melee Attack
+Range: Engaged
+Target: One enemy engaged with you
+Attack: Custom - @{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: MD
+
+Custom 1:
+Hit:
+- The target is dazed until the end of your next turn, and you can make an improved attack against a second target.
+
+Custom 2:
+Miss:
+- Your attack action is over; the feint was a screw-up.
+
+Name: Slick Feint - **Act II**
+Type: Custom - Follow-Up
+Action: Custom - Melee Attack
+Range: Engaged
+Target: A different enemy from the first target that is engaged with you
+Attack: Custom - @{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP] + 2[FEINT]
+VS: AC
+
+Custom 1:
+Hit:
+- [[@{MBA-hit}]] damage
+
+Custom 2:
+Miss:
+- [[@{level}[LVL]]] damage
+
+
+-------------------------BLACK VEIL-------------------------
+
+Name: Black Veil
+Type: Encounter
+Action: Custom - Interrupt action; Requires Momentum; Shadow
+
+Custom 1:
+Trigger:
+- An enemy hits you with an attack.
+
+Custom 2:
+Effect:
+- You take half damage from the attack, and make the following attack against the target.
+- **Attack:** [[1d20+@{CHA-mod}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]+?{Modifiers|0}[MOD]+@{E-DIE}]] vs PD
+
+Custom 3:
+Hit:
+- Negative energy damage equal to the damage of the target’s attack, plus [[@{CHA-mod}[CHA]*@{LVL-multiplier}[LVL]]]
+
+-------------------------STAR MANTLE-------------------------
+
+Name: Star Mantle
+Type: Encounter
+Action: Custom - Close-quarters power; Quick action; Shadow
+
+Custom 1:
+Effect:
+- Until the end of your next turn, natural odd attack rolls against you miss.
+- **Adventurer Feat:** Gain momentum when you use this power.
+
+-------------------------SWIFT DODGE-------------------------
+
+Name: Swift Dodge
+Type: At-Will
+Action: Custom - Interrupt Action; Requires Momentum
+
+Custom 1:
+Trigger:
+- You are hit by an attack against AC.
+
+Custom 2:
+Effect:
+- The attacker must reroll the attack.
+
+
+-------------------------SNEAKY SETUP-------------------------
+
+Name: Sneaky Setup
+Type: At-Will
+Action: Custom - Interrupt action; You must spend your Momentum
+Range: Engaged
+
+Custom 1:
+Trigger:
+- An ally makes an attack against an enemy you are engaged with.
+
+Custom 2:
+Effect:
+- The ally gains advantage on the attack.
+
+
+-------------------------CHARGING SHADOW-------------------------
+
+Name: Charging Shadow
+Type: Encounter
+Action: Custom - Melee attack; Shadow
+Target: One nearby or far away enemy
+Attack: Custom - @{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: AC
+
+Custom 1:
+Hit:
+- [[@{MBA-hit} + @{CHA-mod}[CHA]*@{LVL-multiplier}[LVL]]] damage
+
+Custom 2:
+Miss:
+- [[@{level}[LVL]]] damage
+
+Custom 3:
+Custom - (blank):
+- **Champion Feat:** After the attack, you can teleport to engage the enemy.
+
+
+-------------------------KILLING BLOW-------------------------
+
+Name: Killing Blow
+Type: Daily
+Action: Custom - Melee Attack
+Range: Engaged
+Attack: Custom - @{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+VS: AC
+
+Custom 1:
+Hit:
+- [[@{MBA-hit}]] damage. If the target has 250 hit points or less left after the attack, it starts making last gasp saves (16+; see Core Book p. 200). After the fourth failed save, it dies. If the target has more than 250 hit points, you can choose to either deal 100 extra damage or cause the target to lose its next standard action.
+
+Custom 2:
+Miss:
+- You have advantage on your next attack against the target.
+
+
+DLANOR - PALLY COMMANDER MULTICLASS
+
+M-BAMOD
+[[({@{STR-mod},@{CHA-mod}}kl1)]]
+
+CUSTOM ATTACK MODIFIERS
+@{M-BAMOD}[MMOD]+@{level}[LVL]+@{MWEAP-mod}[WEAP]
+
+
+CUSTOM DAMAGE MODIFIERS
+[[@{MBA-hit}]]
+[[@{level}[LVL]]]
+
+
+/w Dlanor A. Knox &{template:default} {{name=Commands}} {{Reminder=When you lead troops against demons, devils, or undead, you can use one command without paying its standard command point cost during that battle. **You also start each battle with 1 command point**}}{{Commands=[You Set Them Up, I Finish (4)](~Dlanor A. Knox|C-You-Set-Them-Up) [You're the Hero! (1)](~Dlanor A. Knox|C-Youre-the-Hero)}}
+
+!# [Clear Your Mind (2)](~Dlanor A. Knox|C-Clear-Your-Mind)
+
+/w Dlanor A. Knox &{template:default} {{name=Tactics}} {{Reminder=When you lead troops against demons, devils, or undead, you can use one tactic without expending it during that battle.}} {{Tactics=[Inspiring Leadership (16+)](~Dlanor A. Knox|T-Inspiring-Leadership)}}
+
+!# [Enforce Clarity (16+)](~Dlanor A. Knox|T-Enforce-Clarity)
+
+/w Dlanor A. Knox &{template:default} {{name=Smites}} {{Reminder=When you activate your Smite Evil, choose one of your Smite powers and gain its effect. You must choose the smite power before making the attack roll, and before you know whether your smite hits or misses.}} {{Smite=[Smite Evil](~Dlanor A. Knox|Smite-Evil)}} {{Smite powers=[True Smite](~Dlanor A. Knox|S-True-Smite) [Blessed Smite](~Dlanor A. Knox|S-Blessed-Smite) [Inquisitor's Smite](~Dlanor A. Knox|S-Inquisitors-Smite)}}
+
+!# fearless and some level 9
+
+Your next paladin melee attack deals [[({0,floor(1-abs(@{level}-1))}dl1)*1d12 + [[floor(@{level}/2)]]d12]] extra damage on a hit. In addition, you can activate one smite power of your choice, which can be True Smite below, or a smite power gained through talents.
+
+/w Dlanor A. Knox &{template:default} {{name=Spells}} {{Reminder=**Adventurer Feat** You gain the at-will attack spell of a cleric domain as a once-per-battle bonus spell. (This feat requires the Cleric Training, Divine Domain or Ranger Ex Cathedral talent)}} {{Spells=[Eyes of the Judge](~Dlanor A. Knox|Eyes-of-the-Judge) [Sacred Mantra](~Dlanor A. Knox|Sacred-Mantra)}}
+
+[[ [[{{@{Dlanor A. Knox|level},10}<2}*1]]d4 + [[floor((@{Dlanor A. Knox|level} - 1)/2)]]d8 + [[{{@{Dlanor A. Knox|level},0}>9}*1]]d8 + @{Dlanor A. Knox|WIS-mod}*@{Dlanor A. Knox|LVL-multiplier} ]] holy damage
diff --git a/voice_changer.sh b/voice_changer.sh
new file mode 100755
index 0000000..b381dc5
--- /dev/null
+++ b/voice_changer.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+set -e
+
+MIC_NAME=""
+MIC_SOURCE=""
+SINK_SOURCE=""
+SWAP_FLAG=0
+
+function load_sinks ()
+{
+ if [[ -z "$(pactl list short modules | grep "VoiceChangerSink")" ]]; then
+ pactl load-module module-null-sink sink_name=VoiceChangerSink
+ else
+ echo -e "\nSinks already loaded."
+ fi
+}
+
+function configure_mic ()
+{
+ MIC_NAME=$( pactl list source-outputs | grep -B 17 "WEBRTC VoiceEngine" | grep "Source Output \#" | sed -r "s|^.*\#||g" )
+ MIC_SOURCE=$( pactl list source-outputs | grep -B 17 "WEBRTC VoiceEngine" | grep "Source\:" | sed -r "s|^.*\:\ ||g" )
+ SINK_SOURCE=$( pactl list sources | grep -B 3 "VoiceChangerSink.monitor" | grep "Source \#" | sed -r "s|^.*\#||g" )
+ MIC_DEFAULT=$( pactl list sources | grep -B 3 "alsa_input.usb-C-Media_Electronics_Inc._YMC_1040-00.mono-fallback" | grep "Source \#" | sed -r "s|^.*\#||g" )
+}
+
+function toggle ()
+{
+ if [[ "$MIC_SOURCE" -ne "$SINK_SOURCE" ]]; then
+ pactl move-source-output "$MIC_NAME" "VoiceChangerSink.monitor" && $HOME/stuf/scripts/notification_wrapper.sh "\nMICROPHONE\nHAS BEEN MOVED TO SINK\n$SINK_SOURCE" "VOICHANG"
+ else
+ pactl move-source-output "$MIC_NAME" "$MIC_DEFAULT" && $HOME/stuf/scripts/notification_wrapper.sh "\nMICROPHONE\nHAS BEEN MOVED TO SINK\n$MIC_DEFAULT" "VOICHANG"
+ fi
+}
+
+function normal ()
+{
+ sox -t pulseaudio default -t pulseaudio VoiceChangerSink
+}
+
+function bladewolf ()
+{
+ if [[ $SWAP_FLAG -eq 1 || -z $( pgrep -f "sox" ) ]]; then
+ pgrep -f "sox" >/dev/null 2>&1 && pkill -f "sox"
+ while [[ -n $(pgrep -f "sox") ]]; do
+ :
+ done
+ nohup sh -c "sox -t pulseaudio default -p pitch -225 | sox - -m -t pulseaudio default -t pulseaudio VoiceChangerSink pitch +75 echo 0.4 0.8 40 0.8 gain +7.5 bass +25" >/dev/null 2>&1 &
+ else
+ echo -e "\nOne voice is already loaded and a swap wasn't explicitly requested."
+ fi
+}
+
+function demonbot ()
+{
+ if [[ $SWAP_FLAG -eq 1 || -z $( pgrep -f "sox" ) ]]; then
+ pgrep -f "sox" >/dev/null 2>&1 && pkill -f "sox"
+ while [[ -n $(pgrep -f "sox") ]]; do
+ :
+ done
+ nohup sh -c "sox -t pulseaudio default -p pitch -225 | sox - -m -t pulseaudio default -t pulseaudio VoiceChangerSink pitch -75 echo 0.4 0.8 40 0.8 gain +7.5 bass +25" >/dev/null 2>&1 &
+ else
+ echo -e "\nOne voice is already loaded and a swap wasn't explicitly requested."
+ fi
+}
+
+function looper ()
+{
+ # gain +7.5
+ if [[ $SWAP_FLAG -eq 1 || -z $( pgrep -f "sox" ) ]]; then
+ pgrep -f "sox" >/dev/null 2>&1 && pkill -f "sox"
+ while [[ -n $(pgrep -f "sox") ]]; do
+ :
+ done
+ nohup sh -c "sox -t pulseaudio default -p pitch +15 | sox - -m -t pulseaudio default -t pulseaudio VoiceChangerSink pitch -15 echo 0.4 0.8 30 0.75 bass +25 treble +12" >/dev/null 2>&1 &
+ else
+ echo -e "\nOne voice is already loaded and a swap wasn't explicitly requested."
+ fi
+}
+
+function unload_sinks ()
+{
+ read -r -p "Are you sure? [y/N] " response
+ response=${response,,} # tolower
+ if [[ "$response" =~ ^(yes|y)$ ]]; then
+ pactl move-source-output "$MIC_NAME" "$MIC_DEFAULT" && $HOME/stuf/scripts/notification_wrapper.sh "\nMICROPHONE\nHAS BEEN MOVED TO SINK\n$MIC_DEFAULT" "VOICHANG"
+ pgrep -f "sox" >/dev/null 2>&1 && pkill -f "sox"
+ while [[ -n $(pgrep -f "sox") ]]; do
+ :
+ done
+ pactl list short modules | grep "VoiceChangerSink" | cut -f1 | xargs -L1 pactl unload-module
+ echo -e "\nSinks unloaded."
+ else
+ echo -e "\nSink unloading aborted."
+ fi
+}
+
+[[ -z $( grep -E "^alsa_output.pci-0000_03_04.0.analog-stereo$" $HOME/.config/pulse/* ) || -z $( grep -E "^alsa_input.pci-0000_03_04.0.iec958-stereo$" $HOME/.config/pulse/* ) ]] \
+&& $HOME/stuf/scripts/notification_wrapper.sh "\nSYSTEM CHANGE DETECTED, TERMINATING PROGRAM" "VOICHANG" && exit
+
+load_sinks
+looper
+configure_mic
+
+while getopts "hlunrt" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send sinks] [-l load sinks] [-u unload sinks] [-r reset sinks]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ l) load_sinks; exit ;; # LOAD ALL SINKS
+ u) unload_sinks; exit ;; # UNLOAD ALL SINKS
+ # n) notify_sinks; exit ;; # LIST ALL SINKS VIA WRAPPER
+ # r) reset_sinks; exit ;; # RESET ALL SINKS
+ t) toggle; exit;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+
+#trap 'echo "penis"' SIGINFO
+#trap cleanup EXIT
diff --git a/wide_compiler.sh b/wide_compiler.sh
new file mode 100755
index 0000000..dfbd6f5
--- /dev/null
+++ b/wide_compiler.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+FILE=$(readlink -f "$1")
+DIR=$(dirname "$FILE")
+BASE="${FILE%.*}"
+SHEBANG=$(sed -n 1p "$FILE")
+
+cd "$DIR" || exit
+
+shebangtest() {
+ case "$SHEBANG" in
+ \#\!*) "$FILE" ;;
+ *) sent "$FILE" 2>/dev/null & ;;
+ esac
+}
+
+case "$FILE" in
+ *\.c) cc "$FILE" -o "$BASE" && "$BASE" ;;
+ *\.py) python "$FILE" ;;
+ *\.go) go run "$FILE" ;;
+ *) shebangtest ;;
+esac
+
+notify-send "$FILE"
+
diff --git a/wide_jump.sh b/wide_jump.sh
new file mode 100755
index 0000000..a5f6ca6
--- /dev/null
+++ b/wide_jump.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+
+# the first wide name that actually makes sense
+
+# IMPLEMENT CACHE
+# TEST TO SEE IF SORTING FUCKS SHIT UP
+# A LOTTA ERROR HANDLING, ESPECIALLY BEFORE THE CONNECT
+# SWITCH PROTOCOLS TO CASE STATEMENT
+# TEST MULTIPLE RDP CONNECTIONS AT THE SAME TIME
+
+# set -e
+export TERM=rxvt-unicode
+
+CMD_RDP=( remmina --no-tray-icon -c )
+CMD_SSH=( ssh )
+CMD_NEW_TERMINAL=( urxvt -e bash -rcfile "$HOME/.bashrc.JUMP" )
+
+FZF_FLAGS="--cycle --layout=reverse --border=double --ansi --color=fg:white,bg:black,bg+:magenta,hl:yellow,hl+:green,fg+:white,border:blue,info:green,prompt:green"
+SPAWN_NEW_TERMINAL=0
+RE_CACHE=0
+
+while [ $# -gt 0 ]; do
+ case $1 in
+ -h | --help)
+ echo -e "usage: $0 [-h help] [--spawn-new-terminal] [--regenerate-cache]"
+ exit 0
+ ;;
+ --spawn-new-terminal)
+ SPAWN_NEW_TERMINAL=1
+ ;;
+ --regenerate-cache)
+ RE_CACHE=1
+ ;;
+ *)
+ echo "Invalid option: $1" >&2
+ echo -e "usage: $0 [-h help] [--spawn-new-terminal] [--regenerate-cache]"
+ exit 1
+ ;;
+ esac
+
+ shift
+done
+
+
+CACHE="/tmp/serverlist.txt"
+
+declare CMD=""
+declare SERVER=""
+declare SERVER_ITEM=""
+declare SERVER_ADDRESS=""
+declare SERVER_NAME=""
+declare -a server_list=()
+
+
+function cache_list () {
+
+ [[ -s "${CACHE}.tmp" ]] && truncate -s 0 $CACHE
+
+ item_list=$( pass grep "^connection_address:" | grep ":$" | sed -r "s|(\\x1b)?\\[[0-9;]*m||g; s|:$||g" | sort )
+
+ for item in ${item_list[@]}; do
+ name=$( get_from_pass "$item" "connection_name" )
+ address=$( get_from_pass "$item" "connection_address" )
+ echo "${item} ===> ${address} (${name})" >> "${CACHE}.tmp"
+ done
+
+ cat "${CACHE}.tmp" | column -t | sed 's/\( *\) /\1/g' > $CACHE
+ rm -f "${CACHE}.tmp"
+}
+
+function get_from_pass () {
+ pass show "$1" | grep "^$2:" | sed -r "s|^$2: ||g"
+}
+
+[[ -s $CACHE && $RE_CACHE -eq 0 ]] || cache_list
+mapfile -t server_list < $CACHE
+
+# pick server
+
+SERVER=$( printf '%s\n' "${server_list[@]}" | fzf $FZF_FLAGS )
+[[ -z $SERVER ]] && echo "Error: No server selected. Exiting..." >&2 && exit -2
+SERVER_ITEM=$( echo "$SERVER" | sed -r "s|^([^ ]*)\s+===>\s+([^ ]*)\s+\(([^ ]*)\)$|\1|g" )
+SERVER_ADDRESS=$( echo "$SERVER" | sed -r "s|^([^ ]*)\s+===>\s+([^ ]*)\s+\(([^ ]*)\)$|\2|g" )
+SERVER_NAME=$( echo "$SERVER" | sed -r "s|^([^ ]*)\s+===>\s+([^ ]*)\s+\(([^ ]*)\)$|\3|g" )
+
+
+# connect to server based on protocol
+
+SERVER_CONNECTION_TYPE=$( get_from_pass "$SERVER_ITEM" "connection_type" )
+
+if [[ $SERVER_CONNECTION_TYPE == "SSH" ]]; then
+
+ CMD=$CMD_SSH
+
+ PAM_LOGIN_ACCOUNT=$( get_from_pass "$SERVER_ITEM" "PAM_username" )
+ SERVER_CONNECTION_PAM_ACCESS_ACCOUNT=$( get_from_pass "$SERVER_ITEM" "connection_PAM_username" )
+ [[ -n $PAM_LOGIN_ACCOUNT && -n $SERVER_CONNECTION_PAM_ACCESS_ACCOUNT ]] && CMD+=( -l "${PAM_LOGIN_ACCOUNT}#${SERVER_CONNECTION_PAM_ACCESS_ACCOUNT}" )
+
+ SERVER_CONNECTION_USER=$( get_from_pass "$SERVER_ITEM" "connection_username" )
+ [[ -n $SERVER_CONNECTION_USER ]] && CMD+=( -l "$SERVER_CONNECTION_USER" )
+
+ [[ -n $PAM_LOGIN_ACCOUNT && -n $SERVER_CONNECTION_PAM_ACCESS_ACCOUNT && -n $SERVER_CONNECTION_USER ]] && echo "ERROR: Multiple login methods detected. Please edit the password item to contain only one" && exit -2
+
+ SERVER_CONNECTION_KEYPATH=$( get_from_pass "$SERVER_ITEM" "connection_key_path" )
+ [[ -n $SERVER_CONNECTION_KEYPATH ]] && CMD+=( -i "$SERVER_CONNECTION_KEYPATH" )
+
+ SERVER_CONNECTION_PORT=$( get_from_pass "$SERVER_ITEM" "connection_port" )
+ [[ -z "$SERVER_CONNECTION_PORT" ]] && SERVER_CONNECTION_PORT=22
+ CMD+=( -p "$SERVER_CONNECTION_PORT" )
+
+ [[ -n $PAM_LOGIN_ACCOUNT && -n $SERVER_CONNECTION_PAM_ACCESS_ACCOUNT && -n $SERVER_CONNECTION_USER ]]
+
+
+ CMD+=( "$SERVER_ADDRESS" )
+
+elif [[ $SERVER_CONNECTION_TYPE == "RDP" ]]; then
+
+ CMD=$CMD_RDP
+
+ SERVER_CONNECTION_PORT=$( get_from_pass "$SERVER_ITEM" "connection_port" )
+ [[ -z "$SERVER_CONNECTION_PORT" ]] && SERVER_CONNECTION_PORT=3389
+
+
+ SERVER_CONNECTION_USER=$( get_from_pass "$SERVER_ITEM" "connection_username" )
+ SERVER_CONNECTION_DOMAIN=$( get_from_pass "$SERVER_ITEM" "connection_domain" )
+ [[ -n "$SERVER_CONNECTION_DOMAIN" ]] && SERVER_CONNECTION_USER="${SERVER_CONNECTION_DOMAIN}\\${SERVER_CONNECTION_USER}"
+
+ SERVER_CONNECTION_ENCRYPTED_PASSWORD="$( pass show "$SERVER_ITEM" | head -n1 | remmina --encrypt-password --no-tray-icon 2>/dev/null | grep "^Encrypted password: " | sed -r "s|^Encrypted password: ||g" )"
+
+ CMD+=( "rdp://${SERVER_CONNECTION_USER}:${SERVER_CONNECTION_ENCRYPTED_PASSWORD}@${SERVER_ADDRESS}:${SERVER_CONNECTION_PORT}" )
+else
+ exit -1
+fi
+
+echo ${CMD[*]}
+
+if [[ $SPAWN_NEW_TERMINAL -eq 1 ]]; then
+ cat "$HOME/.bashrc" > "$HOME/.bashrc.JUMP"
+ echo "export TERM=$TERM" >> "$HOME/.bashrc.JUMP"
+ echo ${CMD[*]} >> "$HOME/.bashrc.JUMP"
+ pass -c "$SERVER_ITEM" && "${CMD_NEW_TERMINAL[@]}" 2>/dev/null
+else
+ pass -c "$SERVER_ITEM" && "${CMD[@]}"
+fi
diff --git a/wide_play.sh b/wide_play.sh
new file mode 100755
index 0000000..e165a24
--- /dev/null
+++ b/wide_play.sh
@@ -0,0 +1,166 @@
+#!/bin/bash
+
+
+# THE FINAL SOLUTION
+# DEPENDENCIES: mpd, mpc, ncmpcpp
+# ANOTHER OPTION WOULD BE TO DMENU ALL THE OPTIONS, SO IT CAN BE RUN WITH ONLY ONE KEYBOARD SHORTCUT
+# AND I CAN'T HELP BUT THINK THAT THERE'S SOME WAY TO UNIFY THE PLAYLIST TYPES AND THE SINGLE-SONG BRANCH AND USE ONLY FLAGS INSTEAD OF IF
+# OH WELL
+
+# xargs -r is a thing
+
+# cdprev INSTEAD OF prev IF YOU'RE BINDING F10
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '---------------------------------------------------------------------------------------'
+ 'Play Song | wide_play.sh | F8'
+ 'Queue Song | wide_play.sh -q | F9'
+ 'Notify-send Queue | wide_play.sh -n | F10'
+ 'Play/Pause Song | wide_play_helper.sh -t | Shift+F8'
+ 'Skip to Next Song | wide_play_helper.sh -n | Shift+F9'
+ 'Go Back to Previous Song | wide_play_helper.sh -p | Shift+F10'
+ 'Spawn ncmpcpp | CUSTOM | Ctrl+F8'
+ 'Toggle Autoplay | wide_play_helper.sh -a | Ctrl+F9'
+ 'Toggle Consume | wide_play_helper.sh -c | Ctrl+F10'
+ 'Play Playlist | wide_play.sh -l | Ctrl+Shift+F8'
+ 'Queue Playlist | wide_play.sh -lq | Ctrl+Shift+F9'
+ 'Clear Playlist | wide_play.sh -c | Ctrl+Shift+F10'
+ 'Increase Volume | wide_play_helper.sh -V | Shift+Upper side mouse button'
+ 'Decrease Volume | wide_play_helper.sh -v | Shift+Lower side mouse button'
+)
+
+playlist_flag=false
+queue_flag=false
+clear_flag=false
+current_song=$(mpc current -f "%position%" | head -n 1)
+
+MUSIC="$HOME/music"
+SONG=""
+ROFI_THEME="BernBlue"
+AUDIO_EXTENSIONS="(\.mp3|\.flac|\.wav|\.m4a|\.mp4|\.wma|\.aac)$"
+
+#E0E04B
+#AAAA00
+
+function play_playlist() {
+
+ $queue_flag || mpc clear # IF QUEUE FLAG IS FALSE, THEN CLEAR THE QUEUE
+ mpc ls "$1" | grep -E "$AUDIO_EXTENSIONS" | $2 | sed -r "s|'|\\\'|g" | xargs -I{} mpc add "{}" # SHUFFLE/SORT AND PIPE EVERY SONG IN THE DIRECTORY (IGNORING ANY NESTED DIRECTORIES) INTO MPC
+ $queue_flag || mpc play # IF QUEUE FLAG IS FALSE, PLAY THE FIRST SONG
+ mpc single off # ENABLE AUTOPLAY
+
+}
+
+function play_song() {
+
+ if ! $queue_flag; then
+ mpc insert "$1"
+ CURR_INDEX="$( mpc current -f "%position%" | head -n1 )"
+ NEXT_INDEX="$( mpc queued -f "%position%" | head -n1 )"
+ [[ -n "$NEXT_INDEX" ]] && mpc move "$NEXT_INDEX" "$CURR_INDEX" && mpc play "$CURR_INDEX" || mpc play
+ else
+ mpc add "$1"
+ fi
+ $queue_flag && mpc single off || mpc single on # TOGGLE AUTOPLAY BASED ON CIRCUMSTANCES
+}
+
+
+while getopts "hsnlqc" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send queue] [-l list] [-q queue] [-c clear]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ s) [[ ! $(pgrep -x ncmpcpp) ]] && /usr/bin/urxvt -e sh -c "/usr/bin/ncmpcpp" ; exit ;;
+ n) "$HOME/stuf/scripts/notification_wrapper.sh" "\n$( mpc playlist -f "%file%" | sed -r "${current_song}s|^|> |g" )" "WIDEPLAY" ; exit ;; # NOTIFY-SEND QUEUE VIA OWN WRAPPER
+ l) playlist_flag=true; ROFI_THEME="BernViolet" ;; # SET PLAYLIST FLAG, CHANGE DMENU COLOR
+ q) queue_flag=true; ROFI_THEME="${ROFI_THEME}Queue" ;; # SET QUEUE FLAG, CHANGE DMENU TEXT
+ c) clear_flag=true; ROFI_THEME="BernRed" ;; # SET CLEAR FLAG, CHANGE DMENU COLOR/TEXT
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+if $clear_flag ; then
+
+ CHOICE=$(echo -e "1. Keep only the currently playing song\n2. Clear everything\n3. Abort" \
+ | rofi -dmenu -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 3 -no-click-to-exit )
+ echo "$CHOICE" | grep "1" &> /dev/null && mpc crop
+ echo "$CHOICE" | grep "2" &> /dev/null && mpc clear && mpc single on
+ exit
+fi
+
+# FOR THE FOLLOWING TO WORK YOU NEED TO HAVE THE FOLLOWING KEYS UNBOUND IN YOUR ROFI CONFIG:
+# kb-accept-custom: "";
+# kb-accept-custom-alt: "";
+# kb-accept-alt: "";
+
+KEYBINDS="-kb-custom-1 Shift+Return -kb-custom-2 Ctrl+Return -kb-custom-3 Alt+Return"
+
+if $playlist_flag ; then
+
+ # FIND EVERY DIRECTORY WITH AN AUDIO FILE IN IT AND PIPE IT INTO ROFI
+ PL_DIR=$( mpc listall | sed -r "s|\/[^\/]+?$|\/|g" | sort -V | uniq \
+ | rofi -dmenu -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 15 -no-click-to-exit $KEYBINDS )
+
+ KEYCODE="$?"
+
+ if [[ -n $PL_DIR ]]; then
+
+ case $KEYCODE in
+ 10)
+ SONG=$( mpc ls "$PL_DIR" | grep -E "$AUDIO_EXTENSIONS" | shuf | head -n1 | sed -r "s|'|\\\'|g" )
+
+ if [[ -n $SONG ]]; then
+ play_song "$SONG"
+ fi
+ ;;
+ 11)
+ PL_PARENT="${PL_DIR%\/*\/}/"
+
+ while IFS= read -r PL_SUBDIR; do
+ play_playlist "$PL_SUBDIR" "sort -V"
+ queue_flag=true
+ done < <( mpc lsdirs "$PL_PARENT" | sort -V )
+ ;;
+ 12)
+ play_playlist "$PL_DIR" "shuf"
+ ;;
+ *)
+ play_playlist "$PL_DIR" "sort -V"
+ ;;
+ esac
+
+ fi
+
+else
+
+ # FIND EVERY AUDIO FILE AND PIPE IT INTO ROFI, IN TWO LINES
+ SONG=$( mpc listall | sort --version-sort | sed -r "s|(^([^\/]+\/)+)(([^\/])+$)|\1\n\3\x0f|g; $ s|.{1}$||" | paste -sd '\n\0' \
+ | rofi -dmenu -sep '\x0f' -eh 2 -i -no-custom -p "" -theme "$ROFI_THEME" -async-pre-read 15 -no-click-to-exit $KEYBINDS )
+
+ KEYCODE="$?"
+
+ SONG=$( echo -e "$SONG" | paste -sd '' )
+
+ if [[ -n $SONG ]]; then
+
+ case $KEYCODE in
+ 10)
+ mpc insert "$SONG"
+ # $queue_flag && mpc single off || mpc single on # TOGGLE AUTOPLAY BASED ON CIRCUMSTANCES
+ ;;
+ 11)
+ # THIS ONLY WORKS WITH LETTER-NUMBER SONG PREFIXES (e.g. MahoAko01, MP02, MiAx03), IF YOU CHANGE YOUR PREFIXING SCHEME ONE DAY YOU'LL NEED TO CHANGE THIS TOO
+
+ PREFIX=$( echo "$SONG" | sed -r "s|^(.*/)([a-zA-Z]+)([0-9]+) .*$|\2|g" )
+ PREFIX_DIR=$( echo "$SONG" | sed -r "s|^(.*/)([a-zA-Z]+)([0-9]+) .*$|\1|g" )
+ $queue_flag || mpc clear # IF QUEUE FLAG IS FALSE, THEN CLEAR THE QUEUE
+ mpc ls "$PREFIX_DIR" | grep -E "/${PREFIX}[0-9]+ .*${AUDIO_EXTENSIONS}" | sort -V | sed -r "s|'|\\\'|g" | xargs -r -I{} mpc add "{}"
+ $queue_flag || mpc play # IF QUEUE FLAG IS FALSE, PLAY THE FIRST SONG
+ mpc single off # ENABLE AUTOPLAY
+
+ ;;
+ *)
+ play_song "$SONG"
+ ;;
+ esac
+ fi
+fi
diff --git a/wide_play_helper.sh b/wide_play_helper.sh
new file mode 100755
index 0000000..a3de252
--- /dev/null
+++ b/wide_play_helper.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '---------------------------------------------------------------------------------------'
+ 'Play/Pause Song | wide_play_helper.sh -t | Shift+F8'
+ 'Skip to Next Song | wide_play_helper.sh -n | Shift+F9'
+ 'Go Back to Previous Song | wide_play_helper.sh -p | Shift+F10'
+ 'Toggle Autoplay | wide_play_helper.sh -a | Ctrl+F9'
+ 'Toggle Consume | wide_play_helper.sh -c | Ctrl+F10'
+ 'Increase Volume | wide_play_helper.sh -V | Shift+Upper side mouse button'
+ 'Decrease Volume | wide_play_helper.sh -v | Shift+Lower side mouse button'
+)
+
+next_song="$(mpc queued)"
+
+while getopts "htnpacvV" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-n notify-send queue] [-l list] [-q queue] [-c clear]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ t) mpc toggle ; exit;;
+ n) [[ -n "$next_song" ]] && mpc next ; exit ;;
+ p) mpc cdprev ; exit ;;
+ a) mpc single ; exit ;;
+ c) mpc consume ; exit ;;
+ v) mpc volume -5 ; exit ;;
+ V) mpc volume +5 ; exit ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
diff --git a/wide_rotation.sh b/wide_rotation.sh
new file mode 100755
index 0000000..333c854
--- /dev/null
+++ b/wide_rotation.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+
+# xfconf-query -c xfce4-desktop -m
+# Start monitoring channel "xfce4-desktop":
+# set: /backdrop/screen0/monitorDP1/workspace0/last-image
+
+
+# look into types of parentheses, $(( )) is math for example
+# shuf is a thing
+# also xargs -r is a thing
+
+
+SHORTCUTS=(
+ 'FUNCTION | TERMINAL EQUIVALENT | RECOMMENDED KEYBIND'
+ '------------------------------------------------------------------------------------------'
+ 'Randomize Pape | pape.sh | NUM-'
+ 'Notify-send Current Pape | pape.sh -n | Ctrl+NUM-'
+ 'Toggle Pape Daemon | pape.sh -d | Shift+NUM-'
+ 'Select Pape | pape.sh -s | Ctrl+Shift+NUM-'
+)
+
+current_desktop="/backdrop/screen0/monitorHDMI-2/workspace0"
+
+current_pape=$(xfconf-query -c xfce4-desktop -p $current_desktop/last-image) # INVALIDATE DUPLICATES
+
+select_flag=false
+daemon_flag=false # SET TOGGLER WITH KILLALL/SPAWN
+
+PAPES="$HOME/papes"
+PAPE=""
+DMENU_APPEARANCE="mononoki;11;#191919;#AAAAAA;#CC6600;#FFFFFF;Choose your pape:"
+
+#E0E04B
+#AAAA00
+
+function rotation_daemon {
+ while true; do
+ xfconf-query -c xfce4-desktop -p $current_desktop/last-image -s "$(find ~/papes -type f -iregex '.*\.\(bmp\|gif\|jpg\|jpeg\|png\)$' | sort -R | head -1)"
+ sleep 180
+ done
+}
+
+
+while getopts "hnds" opt; do
+ case $opt in
+ h) echo -e "usage: $0 [-h help] [-d toggle daemon] [-s select]\n\nSuggested Keyboard Shortcuts:\n"; printf '%s\n' "${SHORTCUTS[@]}"; exit ;; # PRINT HELP IN TERMINAL
+ n) $HOME/stuf/scripts/notification_wrapper.sh "$( sed -r 's|^\/([^\/]+\/)+||g' <<< "$current_pape" )" "WIDEROT"; exit ;; # NOTIFY-SEND QUEUE VIA MY OWN WRAPPER
+ d) daemon_flag=true ;;
+ s) select_flag=true ;;
+ ?) echo "error: option -$OPTARG is not implemented"; exit ;;
+ esac
+done
+
+
+for i in {1..7}
+do
+ declare "dm$i=$( cut -f$i -d';' <<< "$DMENU_APPEARANCE" )" # CONVERT DMENU APPEARANCE STRIP INTO SEPARATE PARAMETERS
+done
+
+
+if $daemon_flag ; then
+ if [[ $(pgrep -f "wide_rotation.sh -d" | wc -l) -ge 3 ]]; then
+ $HOME/stuf/scripts/notification_wrapper.sh "$( pkill -o -f -e "wide_rotation.sh -d" )" "WIDEROT"
+ else
+ rotation_daemon </dev/null >/dev/null 2>&1 & disown
+ $HOME/stuf/scripts/notification_wrapper.sh "$(pgrep -f "wide_rotation.sh -d" )" "WIDEROT"
+ fi
+ exit
+fi
+
+
+if $select_flag ; then
+
+ # FIND EVERY DIRECTORY WITH AN AUDIO FILE IN IT AND PIPE IT INTO DMENU
+ PAPE=$(find $PAPES -type f -iregex ".*\.\(bmp\|gif\|jpg\|jpeg\|png\)$" | grep -v "$current_pape" | sed -r "s|^\/([^\/]+\/)+||g" | sort --version-sort | uniq \
+ | dmenu -fn "$([[ $(fc-match "$dm1" | grep "$dm1") ]] && fc-match -f %{family} "$dm1" || fc-match -f %{family} mono)-$dm2" -l 10 -i -nb "$dm3" -nf "$dm4" -sb "$dm5" -sf "$dm6" -p "$dm7" )
+
+ [[ -n $PAPE ]] && echo $PAPE | sed -r "s|'|\\\'|g" | xargs -I{} find $PAPES -type f -iname "{}" | head -n 1 \
+ | xargs -I{} xfconf-query -c xfce4-desktop -p $current_desktop/last-image -s "{}"
+
+
+else
+
+ # RANDOMIZE IT
+ # find "$IE" -type f | sed "s|^.*\.\/|$IE|g" | grep -vf "$IE/exceptions.txt" | findimagedupes -R -p /bin/feh -t 95% -
+ xfconf-query -c xfce4-desktop -p $current_desktop/last-image -s "$(find $PAPES -type f -iregex '.*\.\(bmp\|gif\|jpg\|jpeg\|png\)$' | sort -R | head -1)"
+fi
diff --git a/winscripts/IISshit1.ps1 b/winscripts/IISshit1.ps1
new file mode 100644
index 0000000..01d6d06
--- /dev/null
+++ b/winscripts/IISshit1.ps1
@@ -0,0 +1,21 @@
+Import-Module WebAdministration
+
+Import-Module -Name WebAdministration
+
+Get-ChildItem -Path IIS:SSLBindings | ForEach-Object -Process `
+{
+ if ($_.Sites)
+ {
+ $certificate = Get-ChildItem -Path CERT:LocalMachine/My |
+ Where-Object -Property Thumbprint -EQ -Value $_.Thumbprint
+
+ [PsCustomObject]@{
+ Sites = $_.Sites.Value
+ CertificateFriendlyName = $certificate.FriendlyName
+ CertificateDnsNameList = $certificate.DnsNameList
+ CertificateNotAfter = $certificate.NotAfter
+ CertificateIssuer = $certificate.Issuer
+ }
+ }
+}
+
diff --git a/winscripts/IISshit2.ps1 b/winscripts/IISshit2.ps1
new file mode 100644
index 0000000..50892df
--- /dev/null
+++ b/winscripts/IISshit2.ps1
@@ -0,0 +1,35 @@
+Import-Module WebAdministration
+
+$donehash="01CC71753ED61E3BE147478ADF2D39C7CE3322B4"
+
+$sites = Get-ChildItem -path IIS:\Sites
+
+Write-Host "++++++++++++++++++++++++++++++++++++++++++++++++++"
+$env:COMPUTERNAME
+Write-Host "++++++++++++++++++++++++++++++++++++++++++++++++++`n"
+
+foreach($site in $sites)
+{
+
+ $site.name
+ $site.physicalPath
+
+
+ Write-Host "--------------------------------------------------"
+ foreach($binding in $site.bindings.Collection)
+ {
+
+
+ if($binding.certificateHash -and $binding.certificateHash -ne $donehash)
+ {
+ $binding.bindingInformation
+ $binding.certificateHash
+ $binding.certificateStoreName
+ (gci Cert:\LocalMachine\My | ? Thumbprint -eq $binding.certificateHash)[0].Subject
+ (gci Cert:\LocalMachine\My | ? Thumbprint -eq $binding.certificateHash)[0].GetExpirationDateString()
+ Write-Host "`n"
+ }
+ }
+ Write-Host "--------------------------------------------------`n"
+}
+
diff --git a/winscripts/OfficeSetup.exe b/winscripts/OfficeSetup.exe
new file mode 100755
index 0000000..2a0a969
--- /dev/null
+++ b/winscripts/OfficeSetup.exe
Binary files differ
diff --git a/winscripts/bulk_rename_by_date_created.ps1 b/winscripts/bulk_rename_by_date_created.ps1
new file mode 100644
index 0000000..0f5ff3b
--- /dev/null
+++ b/winscripts/bulk_rename_by_date_created.ps1
@@ -0,0 +1,4 @@
+$count = 1
+Get-ChildItem -Path 'C:\Users\Jay Jones\Desktop\Screenshots\phase1\*' -Include *.jpg,*.png -File |
+Sort-Object CreationTime |
+Rename-Item -NewName { 'ngimg-{0:D4}{1}' -f $script:count++, $_.Extension }
diff --git a/winscripts/treat_clips.bat b/winscripts/treat_clips.bat
new file mode 100644
index 0000000..86277bb
--- /dev/null
+++ b/winscripts/treat_clips.bat
@@ -0,0 +1,3 @@
+@echo off
+if not exist "treated\" mkdir treated
+for %%i in (*.mp4) do ffmpeg -y -i "%%i" "treated\%%~ni_treated.mp4" \ No newline at end of file