dotfiles

Inspired by this post and the long weekend, I dove into learning bash and writing some dotfiles to make my life a little easier (sometimes).

Some of these were lifted straight from Evan Hahn at the beginning, some were modified, some were just inspired and I tried writing them. Most importantly, all work and I learned some basic bash scripting!

bb

runs the process in the background like bb timer 10m

#!/usr/bin/env bash
set -e
set -u

if test -t 1; then
  exec 1>/dev/null
fi

if test -t 2; then
  exec 2>/dev/null
fi

setsid -f "$@"

getsong

gets a song and the best guess at the metadata as an mp3. reception is not great (read: god awful) when going around NZ, so you gotta have offline backups.

always check because sometimes spotdl gives you a random song.

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
url="$1"
if [[ "$url" == *"spotify.com"* ]]; then
    exec spotdl "$url" --output "$HOME/Music"
else
    exec yt-dlp -f bestaudio -x --audio-format mp3 --embed-metadata --embed-thumbnail --parse-metadata "%(title)s:%(artist)s - %(track)s" -o "$HOME/Music/%(title)s.%(ext)s" "$@"
fi

hoy

prints today's date and puts it in my clipboard

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
date '+%Y-%m-%d' | tee >(wl-copy)

imgup

uploads a compressed version of an image from my pictures directory to imgbb, then puts the link in my clipboard so I can put it in my site. I could probably use it for sending images to people with temp links, but I'm not sure I want that in this dotfile or not.

#!/bin/bash

# Your ImgBB API key
IMGBB_API_KEY="your_api_key_here"

PICTURES_DIR="$HOME/Pictures"
UPLOAD_DIR="$PICTURES_DIR/uped2imgbb"

# Create upload directory if it doesn't exist
mkdir -p "$UPLOAD_DIR"

# If no argument provided, show numbered list
if [ -z "$1" ]; then
    echo "Files in Pictures:"
    mapfile -t files < <(find "$PICTURES_DIR" -maxdepth 1 -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -o -iname "*.webp" \) | sort)

    if [ ${#files[@]} -eq 0 ]; then
        echo "No image files found in $PICTURES_DIR"
        exit 1
    fi

    for i in "${!files[@]}"; do
        echo "$((i+1)). $(basename "${files[$i]}")"
    done

    echo ""
    echo -n "Enter number (or 'q' to quit): "
    read -r selection

    if  "$selection" == "q" ]] ; then
        echo "Cancelled."
        exit 0
    fi

    if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#files[@]} ]; then
        echo "Invalid selection."
        exit 1
    fi

    INPUT_FILE="${files[$((selection-1))]}"

    echo ""
    echo "Convert $(basename "$INPUT_FILE")? [y/n]"
    read -r response

    if [[ ! "$response" =~ ^[Yy]$ ]]; then
        echo "Cancelled."
        exit 0
    fi
else
    # Search for file (case-insensitive)
    INPUT_FILE=$(find "$PICTURES_DIR" -maxdepth 1 -iname "$1" -type f | head -1)

    if [ -z "$INPUT_FILE" ]; then
        echo "Image does not exist in $PICTURES_DIR"
        exit 1
    fi
fi

# Get original file size
ORIGINAL_SIZE=$(stat -c%s "$INPUT_FILE")

TEMP_DIR="/tmp/squoosh-$"
mkdir -p "$TEMP_DIR"

# Compress to WebP with quality 30
echo "Compressing image..."
squoosh-cli --webp '{"quality":30}' "$INPUT_FILE" -d "$TEMP_DIR"

# Get the output filename (squoosh changes extension to .webp)
COMPRESSED_FILE="$TEMP_DIR/$(basename "${INPUT_FILE%.*}").webp"

# Get compressed file size
COMPRESSED_SIZE=$(stat -c%s "$COMPRESSED_FILE")

# Calculate savings
DIFF=$((ORIGINAL_SIZE - COMPRESSED_SIZE))
PERCENT=$(awk "BEGIN {printf \"%.1f\", ($DIFF / $ORIGINAL_SIZE) * 100}")

# Display size comparison
if [ $DIFF -gt 0 ]; then
    echo "Space saved: $(numfmt --to=iec-i --suffix=B $DIFF) (${PERCENT}% reduction)"
else
    DIFF_ABS=${DIFF#-}
    echo "Warning: File is larger by $(numfmt --to=iec-i --suffix=B $DIFF_ABS)"
fi

# Upload to ImgBB
echo "Uploading to ImgBB..."
RESPONSE=$(curl -s --location --request POST "https://api.imgbb.com/1/upload?key=$IMGBB_API_KEY" \
    --form "image=@$COMPRESSED_FILE")

# Extract the URL
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | sed 's/"url":"//;s/"//;s/\\//g')

# Copy to clipboard
echo "$URL" | wl-copy

# Move original file to uploaded folder
mv "$INPUT_FILE" "$UPLOAD_DIR/"
echo "Original moved to: $UPLOAD_DIR/$(basename "$INPUT_FILE")"

# Clean up
rm -rf "$TEMP_DIR"

echo "Done! URL copied to clipboard:"
echo "$URL"

mkdot

makes it easier for me to make new dotfiles, especially while I was playing around

#!/usr/bin/env bash
set -e
set -u
set -o pipefail

filename="$HOME/.dotfiles/$1"
touch "$filename"
chmod +x "$filename"
kate "$filename"

notify

used inside timer and weather to send a notification. might use in other stuff later?

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
title="${1:-Notification}"
description="${2:-$(date --iso-8601=seconds)}"
notify-send --expire-time=5000 "$title" "$description"

now

shows me a calendar of this month with today highlighted. been using it for when I'm making lesson plans.

#!/usr/bin/env bash
set -e
set -u
date "+%l:%M%p on %A, %B %e, %Y"
echo
cal | grep -E --color=always "^|$(date '+%e')"

radio

sometimes (usually?), you just need someone else to make choices for you

#!/usr/bin/env bash
set -e
set -u
set -o pipefail

# Define stations in an associative array
declare -A stations=(
  [00s]='https://stream.revma.ihrhls.com/zc6850'
  [80hh]='https://regiocast.streamabc.net/regc-80s80shiphop3449572-mp3-192-3635250'
  [80s]='https://live.hunter.fm/80s_stream?ag=mp3'
  [90hh]='https://streams.90s90s.de/hiphop/mp3-192'
  [90s]='https://streams.90s90s.de/main/aac-64'
  [classical]='https://cms.stream.publicradio.org/cms.aac'
  [kfai]='https://kfai.broadcasttool.stream/kfai-1'
  [kpop]='https://live.hunter.fm/kpop_stream?ag=mp3'
  [lofi]='https://live.hunter.fm/lofi_high'
  [master]='https://live.hunter.fm/master_stream?ag=mp3'
  [pop]='https://live.hunter.fm/pop_stream?ag=mp3'
  [pop2k]='https://live.hunter.fm/pop2k_stream?ag=mp3'
  [trance]='https://fr3.1mix.co.uk:8000/stream13'
  [wnyc]='https://opera-stream.wqxr.org/wnycfm-tunein.aac'
  [xmas]='https://christmasfm.cdnstream1.com/2547_64.aac'
)

# If no argument provided, list stations
if [ $# -eq 0 ]; then
  echo "Available stations:"
  printf '%s\n' "${!stations[@]}" | sort
  exit 0
fi

# Get the URL for the requested station
url="${stations[$1]:-}"

if [ -z "$url" ]; then
  echo "don't know $1" 1>&2
  exit 1
fi

exec mpv --msg-level=all=no,statusline=status --term-status-msg='${media-title}' "$url"

timer

just a timer, but pretty handy especially combined with bb

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
sleep "$1"
paplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga &
notify 'timer complete' "$1"

tunes

ipod shuffle on the computer

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
if [ $# -eq 0 ]; then
  folder="$HOME/Music"
else
  folder="$HOME/Music/${1,,}"
fi
exec mpv --shuffle --loop-playlist --no-video "$folder"/*

vidpod

lessen some stress on my computer when i'm listening to a video podcast without watching the video (always)

#!/usr/bin/env bash
set -e
set -u
set -o pipefail
exec mpv --no-video --ytdl-format=worstaudio "$@"

weather

shows the weather and puts it in my clipboard so I can paste it in my daily notes easier. mostly used to track my mood against the weather here because I was realizing huge shifts in my mood when I moved to New Zealand. it also gives me some notifications about the weather since I usually check before I go to work. still loads slow, so maybe there's some optimization to be had, but I needed to use JSON because the simpler api kept giving me current conditions, not whole day.

#!/usr/bin/env bash
set -e
set -u
set -o pipefail

location="${1:-}"

# Get JSON forecast
forecast=$(curl -s "wttr.in/${location}?format=j1&m")

# Parse daily forecast data
temp=$(echo "$forecast" | jq -r '.weather[0].maxtempC')
condition=$(echo "$forecast" | jq -r '.weather[0].hourly | map(.weatherDesc[0].value) | group_by(.) | max_by(length) | .[0]')
wind=$(echo "$forecast" | jq -r '.weather[0].hourly | map(.windspeedKmph | tonumber) | max')
uv=$(echo "$forecast" | jq -r '.weather[0].hourly | map(.uvIndex | tonumber) | max')
rain=$(echo "$forecast" | jq -r '.weather[0].hourly | map(.precipMM | tonumber) | add')
humidity=$(echo "$forecast" | jq -r '.weather[0].hourly | map(.humidity | tonumber) | max')

# Build display string
weather="+${temp}°C ${condition} Wind: ${wind}km/h UV: ${uv} Rain: ${rain}mm Humidity: ${humidity}%"
echo "$weather"
echo -n "$weather" | wl-copy

# Build alert message
alerts=()

if [ "$uv" -gt 2 ]; then
    alerts+=("NEED SUNSCREEN")
fi

if (( $(echo "$rain >= 1" | bc -l) )); then
    alerts+=("NEED UMBRELLA")
fi

if [ "$temp" -gt 20 ]; then
    alerts+=("NEED WATER")
fi

if [ "$temp" -lt 15 ]; then
    alerts+=("NEED JACKET")
fi

if [ "$humidity" -gt 70 ] && [ "$temp" -gt 25 ]; then
    alerts+=("NEED FAN")
fi

# Send notification if there are alerts
if [ ${#alerts[@]} -gt 0 ]; then
    message=$(IFS=

\n'; echo "${alerts[*]}")
    notify "Weather Alert" "$message"
fi