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 # preview functions
. "sh/preview.sh" . "sh/preview.sh"
# varia functions # userprompt functions
. "sh/varia.sh" . "sh/userprompt.sh"
# tags # tags
. "sh/tags.sh" . "sh/tags.sh"
@@ -143,7 +143,7 @@ if [ "${1:-}" = "--open-part" ]; then
[ "$res" ] || exit [ "$res" ] || exit
tmpfile=$(mktemp) tmpfile=$(mktemp)
nm_message_get_part "$messageid" "$res" > "$tmpfile" 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? # TODO: should we clean up at some point?
open "$tmpfile" >/dev/null 2>/dev/stdout open "$tmpfile" >/dev/null 2>/dev/stdout
fi fi
@@ -203,7 +203,7 @@ if [ "${1:-}" = "--purge" ]; then
shift shift
query="tag:$TAG_DEL and ($(echo " $*" | sed "s/\s\(\S\)/ thread:\1/g"))" query="tag:$TAG_DEL and ($(echo " $*" | sed "s/\s\(\S\)/ thread:\1/g"))"
files=$(nm_files_all "$query") 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 printf '%s' "$files" | xargs -d "\n" rm
nm_new nm_new
fi fi
@@ -301,8 +301,8 @@ if [ "${1:-}" = "--list-deleted" ]; then
--bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_DOWN_HP:half-page-down" \
--bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_UP_HP:half-page-up" \
--bind="$KEYS_ENTER:" \ --bind="$KEYS_ENTER:" \
--bind="$KEYS_PURGE_ALL:select-all+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-silent($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_ENTER_ALTERNATIVE:execute-silent($0 --undelete {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \
--bind="$KEYS_VIEW_LOGS:execute:$0 --view-logs" --bind="$KEYS_VIEW_LOGS:execute:$0 --view-logs"
exit exit
+7
View File
@@ -27,5 +27,12 @@ EOF
) )
export AWK_PARTNR export AWK_PARTNR
AWK_ATTACH=$(
cat <<'EOF'
@@include awk/attach.awk
EOF
)
export AWK_ATTACH
export AWK_LOADED=1 export AWK_LOADED=1
fi 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 # Send email
# @argument $1: Path to email file # @argument $1: Path to email file
# @argument $2: if set, don't prompt the user (optional) # @argument $2: if set, don't prompt the user (optional)
send() { send() {
if [ ! "${2:-}" ] && ! ynprompt "Do you want to send this email?" "$($CATEMAIL "$tmpfile")"; then if [ ! "${2:-}" ]; then
return # 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 fi
$MSMTP -t --read-envelope-from -X "$SMTPLOGFILE" < "$1" >/dev/null 2>&1 || true $MSMTP -t --read-envelope-from -X "$SMTPLOGFILE" < "$1" >/dev/null 2>&1 || true
log=$(tail -1 "$SMTPLOGFILE") log=$(tail -1 "$SMTPLOGFILE")
@@ -11,7 +39,7 @@ send() {
return return
fi fi
# Handle errors # 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 case "$res" in
0) send "$1" ;; 0) send "$1" ;;
1) $EDITOR "$1"; 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")" COLTAGS="$(printf '\033[38;5;%sm' "$ANSICOLORTAGS")"
COLRESET="$(printf '\033[0m')" 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 ANSICOLORDATE ANSICOLORCNTS ANSICOLORFROM ANSICOLORSUBJ ANSICOLORTAGS
export COLDATE COLCNTS COLFROM COLSUBJ COLTAGS COLRESET export COLDATE COLCNTS COLFROM COLSUBJ COLTAGS COLRESET
export YESSTRING NOSTRING ABORTSTRING EDITSTRING ATTACHSTRING
export THEME_LOADED=1 export THEME_LOADED=1
fi 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
}