Compare commits

..

3 Commits

Author SHA1 Message Date
amin 2ce0c190ea feat: attachment 2026-03-21 22:05:49 +01:00
amin 9f4262566c improved: user prompts 2026-03-20 22:37:57 +01:00
amin 3b72059307 bugfix: background exe 2026-03-20 12:16:40 +01:00
7 changed files with 149 additions and 54 deletions
+59
View File
@@ -0,0 +1,59 @@
# This awk scripts adds an attachment to an email message
#
# Variables:
# fpath: path to file
# Include data
function attach() {
cmd = "file --brief --mime-type '"fpath"'"
suc = cmd | getline ctype
close(cmd)
if (suc != 1)
exit 1
print "Content-Type: "ctype end
print "Content-Transfer-Encoding: base64"end
filename = a[split(fpath, a, "/")]
gsub(/[^[:alnum:]\.\-\_\ ]/, "", filename)
print "Content-Disposition: attachment; filename=\""filename"\""end
print "MIME-Version: 1.0"end
print ""end
cmd = "base64 '"fpath"'"
while ((cmd | getline line) == 1)
print line end
close(cmd)
}
# Compute fresh boundary string
function genboundary() {
return sprintf("===============%020d==", int(exp(20*log(10))*rand()))
}
BEGIN { srand(); gsub(/'/, "'\\''", fpath); }
/^\r?$/ && !inbody { inbody = 1; end = $0 }
/^\r?$/ && !boundary {
nb = genboundary()
print "MIME-Version: 1.0"end
print "Content-Type: multipart/mixed; boundary=\""nb"\""end
print ""end
print "--"nb end
print "Content-Type: text/plain"end
print "Content-Transfer-Encoding: 7bit"end
}
/^Content-Type:/ && !inbody {
match($0, "boundary=\"[^\"]+\"")
boundary = RSTART ? substr($0, RSTART+10, RLENGTH-11) : ""
}
$0 ~ "^--"boundary"--" {
print "--"boundary end
attach()
print ""end
}
{ print }
END {
if (!nb) exit
print ""end
print "--"nb end
attach()
print ""end
print "--"nb"--"end
}
+6 -6
View File
@@ -32,8 +32,8 @@ set -eu
# preview functions
. "sh/preview.sh"
# varia functions
. "sh/varia.sh"
# userprompt functions
. "sh/userprompt.sh"
# tags
. "sh/tags.sh"
@@ -143,7 +143,7 @@ if [ "${1:-}" = "--open-part" ]; then
[ "$res" ] || exit
tmpfile=$(mktemp)
nm_message_get_part "$messageid" "$res" > "$tmpfile"
if ynprompt "Are you sure you want to open this file?" "$($0 --preview-file-info "$tmpfile")"; then
if $0 --preview-file-info "$tmpfile" | ynprompt "Are you sure you want to open this file?"; then
# TODO: should we clean up at some point?
open "$tmpfile" >/dev/null 2>/dev/stdout
fi
@@ -203,7 +203,7 @@ if [ "${1:-}" = "--purge" ]; then
shift
query="tag:$TAG_DEL and ($(echo " $*" | sed "s/\s\(\S\)/ thread:\1/g"))"
files=$(nm_files_all "$query")
if ynprompt "Are you sure you want to purge these files?" "$files"; then
if echo "$files" | ynprompt "Are you sure you want to purge these files?"; then
printf '%s' "$files" | xargs -d "\n" rm
nm_new
fi
@@ -301,8 +301,8 @@ if [ "${1:-}" = "--list-deleted" ]; then
--bind="$KEYS_DOWN_HP:half-page-down" \
--bind="$KEYS_UP_HP:half-page-up" \
--bind="$KEYS_ENTER:" \
--bind="$KEYS_PURGE_ALL:select-all+execute-silent($0 --purge {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \
--bind="$KEYS_DELETE:execute-silent($0 --purge {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \
--bind="$KEYS_PURGE_ALL:select-all+execute($0 --purge {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \
--bind="$KEYS_DELETE:execute($0 --purge {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \
--bind="$KEYS_ENTER_ALTERNATIVE:execute-silent($0 --undelete {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \
--bind="$KEYS_VIEW_LOGS:execute:$0 --view-logs"
exit
+7
View File
@@ -27,5 +27,12 @@ EOF
)
export AWK_PARTNR
AWK_ATTACH=$(
cat <<'EOF'
@@include awk/attach.awk
EOF
)
export AWK_ATTACH
export AWK_LOADED=1
fi
+31 -3
View File
@@ -1,9 +1,37 @@
# Add attachment
# This function updates the email file. Errors are suppressed silently, and the
# email will not be altered.
#
# @argument $1: email file
attatchto() {
f="$($FZF \
--bind="$KEYS_ENTER:accept" \
--bind="$KEYS_CANCEL:abort" \
--bind="$KEYS_DOWN_HP:half-page-down" \
--bind="$KEYS_UP_HP:half-page-up" \
--walker=file \
--walker-root="$HOME" || true)"
[ -r "$f" ] || return 0
eml=$(mktemp)
awk -v fpath="$f" "$AWK_ATTACH" < "$1" > "$eml" || return 0
mv "$eml" "$1"
}
# Send email
# @argument $1: Path to email file
# @argument $2: if set, don't prompt the user (optional)
send() {
if [ ! "${2:-}" ] && ! ynprompt "Do you want to send this email?" "$($CATEMAIL "$tmpfile")"; then
return
if [ ! "${2:-}" ]; then
# Prompt user
undecided=1
while [ "$undecided" ]; do
case "$($CATEMAIL "$1" | multiprompt "Do you want to send this email?" "$YESSTRING\n$EDITSTRING\n$ATTACHSTRING\n$ABORTSTRING")" in
0) undecided= ;;
1) $EDITOR "$1" ;;
2) attatchto "$1" ;;
3) return ;;
esac
done
fi
$MSMTP -t --read-envelope-from -X "$SMTPLOGFILE" < "$1" >/dev/null 2>&1 || true
log=$(tail -1 "$SMTPLOGFILE")
@@ -11,7 +39,7 @@ send() {
return
fi
# Handle errors
res=$(printf "0 Retry\n1 Edit email\n2 Abort\n" | multiprompt "Could not send the email. What's next?" "$(echo "$log" | $CATLOG)")
res=$(echo "$log" | $CATLOG | multiprompt "Could not send the email. What's next?" "Retry\n$EDITSTRING\n$ABORTSTRING")
case "$res" in
0) send "$1" ;;
1) $EDITOR "$1"; send "$1" ;;
+7
View File
@@ -12,8 +12,15 @@ if [ ! "${THEME_LOADED:-}" ]; then
COLTAGS="$(printf '\033[38;5;%sm' "$ANSICOLORTAGS")"
COLRESET="$(printf '\033[0m')"
YESSTRING="${YESSTRING:-$(printf '\033[32mYes\033[0m')}"
NOSTRING="${NOSTRING:-$(printf '\033[31mNo\033[0m')}"
ABORTSTRING="${ABORTSTRING:-$(printf '\033[31mAbort\033[0m')}"
EDITSTRING="${EDITSTRING:-$(printf '\033[33mEdit email\033[0m')}"
ATTACHSTRING="${ATTACHSTRING:-$(printf '\033[35mAttach file\033[0m')}"
export ANSICOLORDATE ANSICOLORCNTS ANSICOLORFROM ANSICOLORSUBJ ANSICOLORTAGS
export COLDATE COLCNTS COLFROM COLSUBJ COLTAGS COLRESET
export YESSTRING NOSTRING ABORTSTRING EDITSTRING ATTACHSTRING
export THEME_LOADED=1
fi
+39
View File
@@ -0,0 +1,39 @@
# Various functions
# This prompts the user for a series of options.
# The content for the preview is read from stdin.
# The possible options are given as newlineline-delimited string.
# The function prints the line number (zero-based) of the selected option.
#
# @argument $1: Question
# @argument $2: Options
multiprompt() {
pf=$(mktemp)
cat > "$pf"
echo "$2" | awk '{ print FNR-1, $0 }' | $FZF \
--reverse \
--no-input \
--header="$1" \
--preview-window='60%,border-line,wrap-word' \
--margin='5%,5%,5%,15%' \
--preview="cat \"$pf\"" \
--bind="$KEYS_PREVIEW_DOWN:preview-down" \
--bind="$KEYS_PREVIEW_UP:preview-up" \
--bind="$KEYS_PREVIEW_DOWN_HP:preview-half-page-down" \
--bind="$KEYS_PREVIEW_UP_HP:preview-half-page-up" \
--bind="$KEYS_PREVIEW_TOGGLE_WRAP:toggle-preview-wrap-word" \
--with-nth=2.. \
--accept-nth=1 \
--header-border='line' \
--bind="ctrl-c,esc,ctrl-q:" \
--color='border:yellow,header:yellow' || true
rm -f "$pf"
}
# This prompts the user for a "yes" or a "no", and shows the content from stdin
# in the preview window.
# @argument $1: Question
# @return: 0 if the user answered "yes" and 1 otherwise.
ynprompt() {
return "$(cat | multiprompt "$1" "$YESSTRING\n$NOSTRING")"
}
-45
View File
@@ -1,45 +0,0 @@
# Various functions
# This prompts the user for a "yes" or a "no"
# @argument $1: Question
# @argument $2: Content to show in preview window
# @return: 0 if the user answered "yes" and 1 otherwise.
ynprompt() {
return "$(printf '0 \033[32myes\033[0m\n1 \033[31mno\033[0m\n' |
$FZF \
--sync \
--bind='start:pos(2)' \
--bind='ctrl-c,ctrl-g,ctrl-q,esc:pos(2)+accept' \
--reverse \
--no-input \
--header="$1" \
--preview-window='60%,border-line,wrap-word' \
--margin='5%,5%,5%,15%' \
--preview="echo \"$2\"" \
--with-nth=2.. \
--accept-nth=1 \
--header-border='line' \
--color='border:yellow,header:yellow')"
}
# This prompts the user for a series of options.
# The options are read from stdin in the form "integer\toption\n".
# Prints number
#
# @argument $1: Question
# @argument $2: Content to show in preview window
multiprompt() {
cat |
$FZF \
--reverse \
--no-input \
--header="$1" \
--preview-window='60%,border-line,wrap-word' \
--margin='5%,5%,5%,15%' \
--preview="echo \"$2\"" \
--with-nth=2.. \
--accept-nth=1 \
--header-border='line' \
--bind="ctrl-c,esc,ctrl-q:" \
--color='border:yellow,header:yellow' | head -1 || true
}