From ec46a11cc6d9b99349f4b46f3270a06b6df7b930 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Tue, 13 Apr 2021 20:44:09 +0000 Subject: [PATCH] Initial verion of notes.sh --- README.md | 7 ++ notes.sh | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 README.md create mode 100755 notes.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..69b0e11 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# notes.sh: plain-text notes with IMAP synchronization + +This project allows you to keep plaintext notes, and attach files to them (like images, PDFs, etc). The plaintext stays plaintext, easily greppable, and with every note in its own file. + +What makes it special is that it uses a Maildir format to store notes. This allows you to easily sync your notes with any mail server, and have your notes accessible on the go through your email client. + +NB: This is alpha software, use with caution. diff --git a/notes.sh b/notes.sh new file mode 100755 index 0000000..52c8842 --- /dev/null +++ b/notes.sh @@ -0,0 +1,227 @@ +#!/bin/bash + +set -e + +DATE_FORMAT="%a, %d %b %Y %H:%M:%S %z" +BASEDIR=~/Maildir/personal/Notes +PID=$$ + + +if [ ! -d "$BASEDIR" ]; then + mkdir -p "$BASEDIR"/{tmp,new,cur} +fi + +uuid() +{ + local N B T + + for (( N=0; N < 16; ++N )) + do + B=$(( $RANDOM%255 )) + + if (( N == 6 )) + then + printf '4%x' $(( B%15 )) + elif (( N == 8 )) + then + local C='89ab' + printf '%c%x' ${C:$(( $RANDOM%${#C} )):1} $(( B%15 )) + else + printf '%02x' $B + fi + + for T in 3 5 7 9 + do + if (( T == N )) + then + printf '-' + break + fi + done + done + + echo +} + +get_header() { + FILE="$1" + HEADER="$2" + echo $(grep -m1 "^${HEADER}: " < "$FILE" | sed -n "s/^${HEADER}: \(.*\)$/\1/p") +} + +get_part() { + FILE="$1" + BOUNDARY="$2" + NUM="$3" + FILTER="\ + BEGIN { \ + rec=0; \ + body=0; \ + } \ + { \ + if (\$0==\"--$BOUNDARY\" || \$0==\"--$BOUNDARY--\") { \ + if (body == 0) { \ + body=1; \ + } \ + rec=rec+1; \ + } \ + else if (body == 1 && rec==$NUM) { \ + print \$0 \ + } \ + }" + + awk "$FILTER" "$FILE" +} + +unpack_part() { + FILE="$1" + DIR="$2" + MIME_TYPE=$(get_header "$FILE" Content-Type) + DISPOSITION=$(get_header "$FILE" Content-Disposition) + + if [[ $DISPOSITION == *"attachment"* ]]; then + ENCODING=$(get_header "$FILE" Content-Transfer-Encoding) + FILENAME=$(echo $DISPOSITION | \ + sed -n 's/^.*filename="\{0,1\}\(.*\)"\{0,1\}$/\1/p') + FILTER="\ + { \ + if (body==1) {print \$0}\ + if (\$0~/^$/) {body=1} \ + } \ + " + + if [[ $ENCODING == *"base64"* ]]; then + awk "$FILTER" "$FILE" | base64 --decode >> "$DIR/$FILENAME" + else + awk "$FILTER" "$FILE" >> "$DIR/$FILENAME" + fi + elif [[ $MIME_TYPE == *"text/plain"* ]]; then + FILTER="\ + { \ + if (body==1) {print \$0}\ + if (\$0~/^$/) {body=1} \ + } \ + " + awk "$FILTER" "$FILE" >> "$DIR/note.md" + elif [[ $MIME_TYPE == *"multipart/mixed"* ]]; then + BOUNDARY=$(echo $MIME_TYPE | sed -n 's/^.*boundary="\(.*\)"$/\1/p') + i=1 + while true; do + TMP=$(mktemp --tmpdir="$DIR") + get_part "$FILE" "$BOUNDARY" "$i" > "$TMP" + ((i++)) + if [ ! -s "$TMP" ]; then + rm "$TMP" + break + fi + #cat "$TMP" + (unpack_part "$TMP" "$DIR") + rm "$TMP" + done + elif [[ $MIME_TYPE == *"multipart/related"* ]]; then + echo "multipart/related not yet supported" + exit 1 + fi +} + + + +unpack_mime() { + FILE="$1" + DIR="$2" + + DATE=$(get_header "$FILE" Date) + SUBJECT=$(get_header "$FILE" Subject) + MIME_TYPE=$(get_header "$FILE" Content-Type) + MESSAGE_ID=$(get_header "$FILE" Message-Id) + + echo "Date: $DATE" > "$DIR/note.md" + if [ ! -z "$MESSAGE_ID" ]; then + echo "Message-Id: $MESSAGE_ID" >> "$DIR/note.md" + fi + echo "Subject: $SUBJECT" >> "$DIR/note.md" + echo "" >> "$DIR/note.md" + + TMP=$(mktemp --tmpdir="$DIR") + cat "$FILE" > "$TMP" + + (unpack_part "$TMP" "$DIR") + rm "$TMP" +} + +new_entry() { + ENTRY_FILE=$(mktemp) + + vim -c ":set syntax=markdown" \ + -c "1 s/^/# /" \ + -c "norm $" \ + "$ENTRY_FILE" + + if [ -s "$ENTRY_FILE" ]; then + MIME_TIMESTAMP=$(LC_ALL="en_US.UTF-8" date "+$DATE_FORMAT") + UNIX_TIMESTAMP=$(date "+%s") + HOSTNAME=$(hostname -s) + SUBJECT=$(head -1 "$ENTRY_FILE") + SUBJECT=${SUBJECT#"# "} + + RESULT=$(mktemp) + echo "Date: $MIME_TIMESTAMP" >> "$RESULT" + echo "MIME-Version: 1.0" >> "$RESULT" + echo "Content-Type: text/plain; charset=utf-8" >> "$RESULT" + echo "Content-Disposition: inline" >> "$RESULT" + echo "Message-Id: <$(uuid)@notes.sh>" >> "$RESULT" + echo "Subject: $SUBJECT" >> "$RESULT" + echo "" >> "$RESULT" + sed "1d" < "$ENTRY_FILE" >> "$RESULT" + + #cat "$RESULT" + + FILENAME="$UNIX_TIMESTAMP.${PID}_1.${HOSTNAME}:2,S" + mv "$RESULT" "$BASEDIR/cur/$FILENAME" + + echo "done" + fi +} + +edit_entry() { + unpack_mime "$1" ~/scratch/notes +} + +list_entries() { + grep -m1 -r "^Subject:" "$BASEDIR" | sed -n 's/^\(.*\):Subject: \(.*\)$/\1:\2/p' + +} + +usage() { + echo "$0 {--new,--list,--edit,--help}" +} + + +while (( "$#" )); do + case "$1" in + -n|--new) + new_entry + exit 0 + ;; + -l|--list) + list_entries + exit 0 + ;; + -e|--edit) + if [ -z "$2" ]; then + echo "Misssing argument for $1" + exit 1 + fi + edit_entry "$2" + exit 0 + ;; + *) + usage + exit 1 + ;; + esac + +done + +usage +exit 1