diff --git a/README.md b/README.md index 63eeb72..f3d1e03 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,26 @@ Conics `(CONsole comICS)` is a program that helps you watch comics from `xkcd.co # Installation -## Step 1 - dependencies +## Dependencies -Conics uses FBI (**F**rame**B**uffer **I**mageviewer) and cURL so install them +Conics requires FBI or FIM to display images using framebuffer, so you need one or both installed -Arch (FBI is in AUR): +Arch (FIM is in AUR): -`yay -S curl fbida` +``` +sudo pacman -S fbida +yay -S fim +``` -Debian, Ubuntu, Mint: +Debian, Ubuntu and derivatives: -`sudo apt-get install fbi` +`sudo apt-get install fbi fim` Other: Idk, please tell me if you know -## Step 2 - main +## Project Run the following commands to install this program: @@ -39,34 +42,38 @@ sudo mv Conics/conics /usr/local/bin/ rm -rf Conics ``` -And you are done! +This will do all the job needed -# Usage +*If you don't have root access, instead of 3rd command run `mv Conics/conics ~/.bin/conics` or move that file to wherever you have acces in $PATH* -## General +# Usage To get help message use `conics -h` or `conics --help` - there's all you need to know about arguments To navigate through downloaded images, use `J` and `K` keys, or `space` to move forward UNTIL THE LAST ONE REACHED. Press `H` in FBI to get more info -## Options +# Options -### Dir (-d) +## Dir (-d) Use to specify the directory where downloaded images will be stored - for example, `~/Pictures` or `.hidden_memes`. Default folder is *.* (current working directory) -### Format (-f) +## Format (-f) Format is used in `mktemp` command, so check it for more information. It needs to end with 3 or more X's that will be replaced with random ~letters and numbers~ alnums. By default format is *img-XXXXX* -### Count (-c) +## Count (-c) Count of comics you want to see. If not used, only one will be shown *(have you done your homework yet?)* -### Time (-t) +## Time (-t) + +Great feature of FBI: if you don't press any key in ARG seconds, it automatically goes to the next one + +## Remove (-r) + +To remove files after closing FBI / FIM. By default it's false, use this flag to change to true -Great feature of FBI: if you don't press any key in <> seconds, it automatically goes to the next one. Closes when reached to the end +## Viewer (-w) -### Remove (-r) -Use it with or without argument. If next arg after -r is `true` or `false`, conics uses it, but if not it'll set it to *true*. However, the default value is *false* \ No newline at end of file diff --git a/conics b/conics index ef72220..e530ce1 100755 --- a/conics +++ b/conics @@ -1,106 +1,242 @@ #!/bin/bash -usage() { +# Default options +# Feel free to change them +conics::defaults() { + FORMAT="img-XXXXX" # Format in which images will be saved (must end with 3+ X for mktemp) + DIR="~/Pictures" # Directory where images will be saved (relative to current directory) + COUNT=5 # Count of images to download at time + TIME=0 # Time to wait for user input for next image in seconds (0 = off) + REMOVE=false # Remove images after closing FBI (true/false) + VERBOSE=false # Verbose output (true/false) + VIEWER="fbi" # Image viewer to use (fbi/fim) +} + + +# Usage of conics +conics::usage() { + # Get default options + conics::defaults + + # Display usage echo "\ Usage: conics [ OPTIONS ] Available options: - -h --help Show this message and exit - -d --dir STR Directory where images will be saved - -f --format STR Format of files (must end with 3+ X) - -c --count INT Count of images to download - -t --time INT Next image if no user input within TIME seconds - -r --remove [B] Remove images after closing FBI (if no ARG after, set to True) + -h, --help Display this help message + -f, --format Format in which images will be saved (default: $FORMAT) + -d, --dir Directory where images will be saved (default: $DIR) + -c, --count Count of images to download (0 = infinity) (default: $COUNT) + -t, --time Time to wait for user input for next image (0 = off) (default: $TIME) + -r, --remove Remove images after closing FBI (default: $REMOVE) + -v, --verbose Verbose output (default: $VERBOSE) + -l, --latest Download latest image (default: false) + -w, --viewer Viewer to use (FBI or FIM) (default: fbi)" + + # Exit with status '$1' if given, else with status 0. + exit ${1:-0} +} -Default FORMAT is img-XXXXX, DIR is . (current) and COUNT is 1" - exit $1 +# Simple logger +conics::log() { + if "$VERBOSE"; then + echo -e "[$(date +%T) at ${FUNCNAME[1]}] $@" > /dev/tty + fi } -# Default options -declare FORMAT="img-XXXXX" DIR="." REMOVE=false -declare -i COUNT=1 TIME=5 - - -# Parse args -while [[ -n "$1" ]]; do - case "$1" in - -h|--help) - usage 0;; - -d|--dir) - if [[ -d "$DIR" ]]; then - DIR="$2" - shift 2 - else - echo "Directory not found: $DIR" - exit 4 - fi - ;; - -f|--format) - if [[ "$2" =~ ^.*XXX+$ ]]; then - FORMAT="$2" - shift 2 - else - echo "Invalid format: $2\nRun 'conics -h'" - exit 3 - fi - ;; - -c|--count) - if [[ "$2" =~ ^[0-9]+$ ]] && (( "$2" > 0 )); then - COUNT="$2" - shift 2 - else - echo "Option 'count' requires INT > 0" - exit 2 - fi - ;; - -t|--time) - if [[ "$2" =~ ^[0-9]+$ ]] && (( "$2" > 0 )); then - TIME="$2" - shift 2 - else - echo "Option 'time' requires INT > 0" - exit 5 - fi - ;; - -r|--remove) - if [[ "$2" =~ ^(true|false)$ ]] && (( "$2" > 0 )); then - REMOVE="$2" - shift 2 - else +# Arguments parser (check 'conics::usage' for more info) +conics::parse() { + while (( "$#" > 0 )); do + conics::log "Parsing argument '$1'" + case "$1" in + -h|--help) + # Show usage and exit with default status 0 + conics::usage + ;; + -d|--dir) + # Check if '$2' is a directory and is writable + if [[ -d "$2" && -w "$2" ]]; then + DIR="$2" + conics::log "DIR set to '$DIR'" + shift 2 + else + echo "Directory not found or unwritable: '$2'" + conics::usage 1 + fi + ;; + -f|--format) + # Check if format ends with 3+ X + if [[ "$2" =~ ^.*XXX+$ ]]; then + FORMAT="$2" + conics::log "FORMAT set to '$FORMAT'" + shift 2 + else + echo "Invalid format: '$2'" + conics::usage 1 + fi + ;; + -c|--count) + # Check if '$2' contains numbers only + if [[ "$2" =~ ^[0-9]+$ ]]; then + # Check if '$2' is not greater than max and is not 0 + if (( "$2" <= "$MAX" && "$2" != 0 )); then + COUNT="$2" + conics::log "COUNT set to '$COUNT'" + shift 2 + else + echo "Argument '$2' for option '$1' is out of range (0-$MAX)" + conics::usage 1 + fi + else + echo "Option '$1' requires a positive integer, given: '$2'" + conics::usage 1 + fi + ;; + -t|--time) + # Check if TIME is a positive integer + if [[ "$2" =~ ^[0-9]+$ ]]; then + TIME="$2" + conics::log "TIME set to '$TIME' seconds" + shift 2 + else + echo "Option '$1' requires a positive integer, given: '$2'" + conics::usage 1 + fi + ;; + -r|--remove) + # Remove images after closing FBI REMOVE=true + conics::log "Images will be removed after closing FBI" shift - fi - ;; - --) - break;; - *) - echo "Unknown option: $1" - usage 1;; - esac -done - - -# Check if directory is writable -if [[ ! -w "$DIR" ]]; then - echo "Directory is unwritable" - exit 3 -fi + ;; + -v|--verbose) + # Show verbose output + VERBOSE=true + conics::log "Verbose output enabled" + shift + ;; + -l|--latest) + # TODO + ;; + -w|--viewer) + # Check if '$2' is a valid viewer + if [[ "$2" =~ ^(fbi|fim)$ ]]; then + VIEWER="$2" + conics::log "VIEWER set to '$VIEWER'" + shift 2 + else + echo "Invalid viewer: '$2'" + conics::usage 1 + fi + ;; + --) + # End of options + break + ;; + *) + # If an option is not recognized, show usage and exit with status 1 + echo "Unknown option: $1" + conics::usage 1 + ;; + esac + done + + # Make all variables read-only + readonly FORMAT DIR COUNT TIME REMOVE VERBOSE +} + + +conics::download() { + # Download images + for (( i = 0; i < "$COUNT"; i++ )); do + IMAGES+=( $(mktemp "$DIR/$FORMAT") ) + curl -so "${IMAGES[-1]}" "$(curl -s "https://xkcd.com/$(( RANDOM % MAX ))/info.0.json" | jq -r '.img')" & + done + + # Wait for all images to be downloaded + unset i + wait +} + + +conics::watch() { + # Get count of images available + local MAX=$(curl -s https://xkcd.com/info.0.json | jq -r '.num') + + # In case of error (e.g. no internet connection) + local status="${PIPESTATUS[0]}" + if [[ "$status" != 0 ]]; then + echo "Failed to connect to xkcd, curl returned error '$status'" >&2 + exit "$status" + fi + + # Infinite loop to watch new images + while :; do + # Set IMAGE array to empty + local -a IMAGES=() + conics::log "Starting download" + conics::download + + conics::log "Download complete, starting FBI/FIM" + case "$VIEWER" in + fbi) + # Start FBI + fbi -t "$TIME" -a "${IMAGES[@]}" + ;; + fim) + if [[ "$TIME" = 0 ]]; then + fim -a "${IMAGES[@]}" + else + fim -c "while(1){display; sleep \"$TIME\"; next; }" -a "${IMAGES[@]}" + fi + ;; + esac + + # This is a bit of a hack, but it works - even for both FBI and FIM! + case "$?" in + 0) + # Closed normally + conics::log "Closed normally, continuing watching" + conics::remove + ;; + 42) + # Closed with Ctrl+C + conics::log "Closed with Ctrl+C, stopping watching" + conics::remove + return 0 + ;; + *) + # Closed with error + conics::log "Closed with error, stopping watching" + ;; + esac + done + # It's needed to call 'remove' function here because it needs 'IMAGES' array and it's local + conics::log "Done!" +} -# Download files -while (( COUNT-- )); do - files+=( $(mktemp "$DIR/$FORMAT") ) - curl -so "${files[-1]}" "$(curl -s "https://xkcd.com/$(( $RANDOM % 2599 ))/info.0.json" | jq . | grep -o '"img": ".*"' | grep -o 'h[^"]*')" & -done -wait -n +conics::remove() { + if "$REMOVE"; then + conics::log "Removing images" + rm -rf "${IMAGES[@]}" + fi +} -# Watch memes ^_^ -fbi -t "$TIME" -1 -a -l <(echo "${files[@]}" | tr " " "\n") &> /dev/null +conics::main() { + conics::defaults + conics::parse "$@" + conics::log "Parsing arguments done" -# Remove files -if $REMOVE; then - rm -rf "${files[@]}" -fi \ No newline at end of file + conics::log "Starting watching" + conics::watch +} + + +# If this script is called directly, execute main function +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + conics::main "$@" +fi