💾 Archived View for jsreed5.org › files › microblog › microblog.sh captured on 2022-06-11 at 21:36:05.

View Raw

More Information

⬅️ Previous capture (2022-06-03)

➡️ Next capture (2023-05-24)

-=-=-=-=-=-=-

#!/bin/bash

# offline microblog-style journaling
# version: 0.7 2022-05-06

# Copyright (c) 2021-2022, Rob S
# distributed under the terms of the simplified (2-clause) BSD license
# gemini://jsreed5.org/files/microblog/license.txt

if [[ ! "$1" ]]; then echo "Error: no timeline file specified."; exit 1; fi
logdir="$(dirname $1)"
timeline="$(basename $1)"
if [[ ! -f "$1" ]]; then
    echo "Error: timeline file not found."
    read -r -p "[t] create new Timeline; else exit: " tlinit
    if [[ "${tlinit,,}" == "t" ]]; then
        id=$(cat /proc/sys/kernel/random/uuid | sed "s/-//g")
        read -r -p "Enter username: " username
        if [[ ! "$username" ]]; then echo "Error: username cannot be empty."; exit 1; fi
        read -r -p "Enter name; else leave name blank: " name
        read -e -p "Enter path to avatar; else generate avatar from ID: " avatarpath
        [[ ! -f "$avatarpath" ]] && read -r -p "[a] generate Alt (simpler) avatar; else generate default: " altavatar
        read -r -p "Enter bio; else leave bio blank: " bio
        read -r -p "Enter location; else leave location blank: " location
        read -r -p "Enter default post length; else length=0 (unlimited): " plength
        plength="$(echo "$plength" | grep -P '^(?!0+$)\d+


)"
        plength="${plength#"${plength%%[!0]*}"}"
        [[ "$plength" ]] || plength=0
        mkdir -p "$logdir"/attachments
        touch "$logdir"/attachments/.anchor
        if [[ -f "$avatarpath" ]]; then
            avatarext="$(basename "$attachpath")"
            avatarext="${avatarext##*.}"
            cp "$avatarpath" "$logdir"/avatar."$avatarext"
        else
            avatarext="svg"
            if [[ "${altavatar,,}" == "a" ]]; then
                colorbase="$(echo "ibase=16;obase=6;${id^^}" | bc)"
                colorbase="${colorbase: -36}"
                echo "<svg width=\"96\" height=\"96\">" > "$logdir"/avatar.svg
                i=0
                while (( i < 36 )); do
                    color="${colorbase:$i:1}"
                    if (( $color == 0 )); then color="e84d3d"; elif (( $color == 1 )); then color="e77f23"; elif (( $color == 2 )); then color="f2c510"; elif (( $color == 3 )); then color="2fcd72"; elif (( $color == 4 )); then color="3599dc"; else color="9c5ab7"; fi
                    echo "    <rect x=\"$(($((16*i))%96))\" y=\"$(($(($((16*i))/96))*16))\" width=\"16\" height=\"16\" fill=\"#$color\" />" >> "$logdir"/avatar.svg
                    i=$((i+1))
                done
                echo "</svg >" >> "$logdir"/avatar.svg
            else
                colorbase="$(echo "ibase=16;obase=4;${id^^}" | bc | tr [0-3] [g-j])"
                while (( ${#colorbase} < 64 )); do colorbase="g$colorbase"; done
                colorbase=$(echo "$colorbase" | sed -e "s/g/e84d3d/g" -e "s/h/f2c510/g" -e "s/i/2fcd72/g" -e "s/j/3599dc/g")
                echo "<svg width=\"96\" height=\"96\">" > "$logdir"/avatar.svg
                i=0
                while (( i < 64 )); do
                    xcorner=$(($((6*i))%96))
                    ycorner=$(($(($((6*i))/96))*24))
                    color="${colorbase:$((i*6)):6}"
                    echo "    <polygon points=\"$xcorner,$ycorner $((xcorner+12)),$((ycorner+12)) $((xcorner+24)),$ycorner\" fill=\"#$color\" />" >> "$logdir"/avatar.svg
                    i=$((i+1))
                    color="${colorbase:$((i*6)):6}"
                    echo "    <polygon points=\"$xcorner,$ycorner $((xcorner+12)),$((ycorner+12)) $xcorner,$((ycorner+24))\" fill=\"#$color\" />" >> "$logdir"/avatar.svg
                    i=$((i+1))
                    color="${colorbase:$((i*6)):6}"
                    echo "    <polygon points=\"$xcorner,$((ycorner+24)) $((xcorner+12)),$((ycorner+12)) $((xcorner+24)),$((ycorner+24))\" fill=\"#$color\" />" >> "$logdir"/avatar.svg
                    i=$((i+1))
                    color="${colorbase:$((i*6)):6}"
                    echo "    <polygon points=\"$((xcorner+24)),$ycorner $((xcorner+12)),$((ycorner+12)) $((xcorner+24)),$((ycorner+24))\" fill=\"#$color\" />" >> "$logdir"/avatar.svg
                    i=$((i+1))
                done
                echo "</svg >" >> "$logdir"/avatar.svg
            fi
        fi
        cat <<EOF > "$logdir"/"$timeline"
#### _HEADER
![avatar](avatar.$avatarext)  
User: $username  
Name: $name  
Bio: $bio  
Location: $location  
\`\`\`
id=$id
plength=$plength
check=0 # when set to 1: when replying to a post, script will confirm if the entered post is correct
recent=0 # when set to 1: script will read back the most recent post after posting
pause=0 # when set to 1: script will wait for a keypress before opening the editor
\`\`\`

---
EOF
        echo "Timeline created."
        echo "Change script behavior by editing variables in timeline header."
        exit 0
    else
        exit 0
    fi
fi
if [[ ! -d "$logdir"/.working/drafts ]]; then
    mkdir -p "$logdir"/.working/drafts
    touch "$logdir"/.working/drafts/.anchor
fi
if [[ ! -d "$logdir"/attachments ]]; then
    mkdir -p "$logdir"/attachments
    touch "$logdir"/attachments/.anchor
fi

curpost="$(grep -c ^#### "$logdir"/"$timeline")"
postheader="$(printf "%08d" $curpost)"
for i in {8..12}; do eval $(sed $i'q;d' "$logdir"/"$timeline"); done

user="$(sed 3'q;d' "$logdir"/"$timeline")"
echo "${user::-1}($id)"
echo "Post: $curpost"
read -r -p "[n] New post, [o] Open draft, [r] Read post, [d] Delete post; else quit: " cfirst
if [[ "${cfirst,,}" == "r" ]]; then
    if (( $curpost == 1 )); then echo "No posts to read."; exit 0; fi
    read -r -p "Enter post number; else read most recent: " readnumber
    readnumber="$(echo "$readnumber" | grep -P '^(?!0+$)\d+


)"
    readnumber="${readnumber#"${readnumber%%[!0]*}"}"
    [[ "$(grep ^####\ $(printf "%08d" "$readnumber") "$logdir"/"$timeline")" ]] || readnumber=$((curpost-1))
    grep --color=never -oPz "(?s)#### $(printf "%08d" $readnumber).*?\n---\n" "$logdir/$timeline"
    exit 0
elif [[ "${cfirst,,}" == "o" ]]; then
    if (( $(ls "$logdir"/.working/drafts | wc -l) )); then
        echo "Warning: changing directory with pushd."
        echo "List of drafts:"
        for draft in "$logdir"/.working/drafts/draft*; do
            echo "$(basename "$draft")    $(dd if="$draft" bs=50 count=1 status=none | tr "\t\n" " ")"
        done
        pushd "$logdir"/.working/drafts > /dev/null
        read -e -p "Enter draft name: " draftname
        popd > /dev/null
        echo "Directory reverted."
        if [[ -f "$logdir"/.working/drafts/"$draftname" ]]; then
            mv "$logdir"/.working/drafts/"$draftname" "$logdir"/.working/_body
            if [[ "$(ls "$logdir"/.working/drafts/ | grep "attach-${draftname#draft-}")" ]]; then
                for file in "$logdir"/.working/drafts/"attach-${draftname#draft-}"*; do mv "$file" "$logdir"/.working/"${file##*attach-${draftname#draft-}-}"; done 
            fi
        else
            echo "Error: draft not found."
            exit 1
        fi
    else
        echo "No drafts to edit."
        exit 0
    fi
elif [[ "${cfirst,,}" == "d" ]]; then
    if (( $curpost == 1 )); then echo "No posts to delete."; exit 0; fi
    read -r -p "Enter post number: " delnumber
    delnumber="$(echo "$delnumber" | grep -P '^(?!0+$)\d+


)"
    delnumber="${delnumber#"${delnumber%%[!0]*}"}"
    if [[ "$(grep ^####\ $(printf "%08d" $delnumber) "$logdir/$timeline")" ]]; then
        echo "Selected post:"
        grep --color=never -oPz "(?s)#### $(printf "%08d" $delnumber)\n.*?\n---\n" "$logdir/$timeline"
        read -r -p "[c] Confirm deletion; else cancel: " confirmation
        if [[ "${confirmation,,}" == "c" ]]; then
            echo "Deleting post and reindexing, this might take a while."
            delplace="$(grep -n ^####\ $(printf "%08d" "$delnumber") "$logdir/$timeline")"
            delplace="${delplace%%:*}"
            dellines="$(grep -oPz "(?s)\n#### $(printf "%08d" "$delnumber").*?\n---\n" "$logdir/$timeline" | wc -l)"
            sed -i -e "$((delplace-1)),+${dellines} d" -e "/Reply: \[$delindex/,+1 d" -e "/Replying to \[$delindex/d" "$logdir/$timeline"
            find "$logdir"/attachments/ -name "$delindex-*" -delete
            delnumber=$((delnumber+1))
            while (( delnumber < curpost )); do
                tempoldindex="$(printf "%08d" "$delnumber")"
                tempnewindex="$(printf "%08d" "$((delnumber-1))")"
                sed -i -e "s/#### $tempoldindex/#### $tempnewindex/" -e "s/Reply: \[$tempoldindexindex/Reply: \[$tempnewindexindex/" -e "s/Replying to \[$tempoldindex/Replying to \[$tempnewindex/g" -e "s/(attachments\/$tempoldindex/(attachments\/$tempnewindex/g" "$logdir/$timeline"
                for file in "$logdir"/attachments/"$tempoldindex"-*; do filename="$(basename "$file")"; mv "$file" "${file%/*}/$tempnewindex-${filename#*-}" 2> /dev/null; done
                delnumber=$((delnumber+1))
            done
            exit 0
        else
            echo "Deletion canceled."
            exit 0
        fi
    else
        echo "No valid post selected, canceling."
        exit 0
    fi
elif [[ "${cfirst,,}" != "n" ]]; then
    exit 0
fi

(( $pause )) && read -r -n 1 -p "Press any key to continue. "
touch "$logdir"/.working/_body
nano -t$ "$logdir"/.working/_body

if (( $plength )); then
     while (( $plength < $(cat "$logdir"/.working/_body | wc -m) )); do
         echo "Error: post too long ($(( $(cat "$logdir"/.working/_body | wc -m)-$plength )) characters over)"
         read -r -p "[i] Ignore, [s] Save draft; else re-edit: " long
         if [[ "${long,,}" == "s" ]]; then
             curdate="$(date +"%Y%m%d%H%M%S%z")"
             mv "$logdir"/.working/_body "$logdir"/.working/drafts/"draft-$curdate"
             echo "Draft saved to draft-$curdate."
             exit 0
         elif [[ "${long,,}" == "i" ]]; then
             break
         else
             nano -Lt$ "$logdir"/.working/_body
         fi
     done
fi

attachcount=$(($(ls "$logdir"/.working | wc -l)-1))
read -r -p "[a] add Attachment, [r] add Reply; else continue: " csecond
while [[ "${csecond,,}" == "a" ]]; do
    read -e -p "Enter path to attachment: " attachpath
    if [[ -f "$attachpath" ]]; then
        cp "$attachpath" "$logdir"/.working/"$(printf "%02d" $attachcount)-$(basename "$attachpath")"
        echo "Attachment added."
        attachcount=$((attachcount+1))
    else
        echo "Attachment not found, skipping."
    fi
    read -r -p "[a] add Attachment, [r] add Reply; else continue: " csecond
done
if [[ "${csecond,,}" == "r" ]]; then
    read -r -p "Enter post number: " replynumber
    replynumber="$(echo "$replynumber" | grep -P '^(?!0+$)\d+


)"
    replynumber="${replynumber#"${replynumber%%[!0]*}"}"
    if [[ "$(grep ^####\ $(printf "%08d" "$replynumber") "$logdir/$timeline")" ]]; then 
        while (( $check )); do
            echo "Selected post:"
            grep --color=never -oPz "(?s)#### $(printf "%08d" $replynumber)\n.*?\n---\n" "$logdir/$timeline"
            read -r -p "[c] Confirm reply number; else change: " confirmation
            if [[ "${confirmation,,}" == "c" ]]; then
                check=0
            else
                read -r -p "Enter post number: " replynumber
            fi
        done
    else
        curdate="$(date +"%Y%m%d%H%M%S%z")"
        echo "Error: post not found, saving post as draft-$curdate."
        mv "$logdir"/.working/_body "$logdir"/.working/drafts/"draft-$curdate"
        while IFS= read -r file; do mv "$file" "$logdir"/.working/drafts/"attach-$curdate-$(basename "$file")"; done < <(find "$logdir"/.working/ -maxdepth 1 -type f)
        exit 1
    fi
fi

read -r -p "[d] Discard post, [s] Save as draft, [t] cross-post as Twtxt; else post: " cthird
if [[ "${cthird,,}" == "d" ]]; then
    find "$logdir"/.working/ -maxdepth 1 -type f -delete
    echo "Post and attachments discarded."
    exit 0
elif [[ "${cthird,,}" == "s" ]]; then
    curdate="$(date +"%Y%m%d%H%M%S%z")"
    mv "$logdir"/.working/_body "$logdir"/.working/drafts/"draft-$curdate"
    while IFS= read -r file; do mv "$file" "$logdir"/.working/drafts/"attach-$curdate-$(basename "$file")"; done < <(find "$logdir"/.working/ -maxdepth 1 -type f)
    echo "Draft saved as draft-$curdate."
    exit 0
fi

if (( $(($(ls "$logdir"/.working/ | wc -l)+$(cat "$logdir"/.working/_body | wc -m)))==2 )); then
    echo "Empty post, skipping."
    rm -f "$logdir"/.working/_body
    exit 0
fi

while IFS= read -r file; do mv "$file" "$logdir"/attachments/"$postheader-$(basename "$file")"; done < <(find "$logdir"/.working/ -maxdepth 1 -type f ! -name "_body")
cat <<EOF >> "$logdir"/"$timeline"

#### $postheader
$(date +"%Y-%m-%d %H:%M:%S %:z")$((( $replynumber )) && printf "  \n%s" "Replying to [$(printf "%08d" "$replynumber")](#$(printf "%08d" "$replynumber"))")

$(cat "$logdir"/.working/_body)$(if [[ $(find "$logdir"/attachments/ -name "$postheader-*" ) ]]; then (( $(cat "$logdir"/.working/_body | wc -m) )) && printf "\n\n"; find "$logdir"/attachments/ -maxdepth 1 -type f -name "$postheader-*" -exec basename {} \; | sort | sed 's/\([0-9]*-[0-9]*-\)\(.*\)\(\..*\)/!\[\2\]\(attachments\/\1\2\3\)/' | sed -z 's/\n!/\n\n!/g'; fi)

---
EOF
if [[ "${cthird,,}" == "t" ]]; then
    echo -e "$(date +%FT%T%:z)\t$(cat "$logdir"/.working/_body | sed -Ez "s/[\n]+/ /g")" >> "$logdir"/_twtxt.sink
fi
if (( $replynumber )); then
    place="$(grep -n ^####\ $(printf "%08d" "$((replynumber+1))") "$logdir/$timeline")"
    sed -i $((${place%%:*}-2))"s/---/Reply: [$postheader](#$postheader)\n\n---/" "$logdir/$timeline"
fi
echo "Post addedd successfully."
if (( $recent )); then
    echo "Most recent post:"
    grep --color=never -oPz "(?s)#### $postheader\n.*?\n---\n" "$logdir/$timeline"
fi

rm -f "$logdir"/.working/_body