trying a simple shell script and fixing archives

This commit is contained in:
Tim Bendt
2025-07-15 22:13:46 -04:00
parent f7474a3805
commit df4c49c3ef
18 changed files with 1273 additions and 389 deletions

38
shell/email_processor.awk Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/awk -f
# Primary email processor using AWK
# Lightweight, portable text processing for cleaning up email content
{
# Remove URL defense wrappers step by step
gsub(/https:\/\/urldefense\.com\/v3\/__/, "")
gsub(/__[^[:space:]]*/, "")
# Extract and shorten URLs to domains
# This processes all URLs in the line
while (match($0, /https?:\/\/[^\/[:space:]]+/)) {
url = substr($0, RSTART, RLENGTH)
# Extract domain (remove protocol)
domain = url
gsub(/^https?:\/\//, "", domain)
# Replace this URL with [domain]
sub(url, "[" domain "]", $0)
}
# Remove any remaining URL paths after domain extraction
gsub(/\][^[:space:]]*/, "]")
# Remove mailto links
gsub(/mailto:[^[:space:]]*/, "")
# Clean up email headers - make them bold
if (/^From:/) { gsub(/^From:[[:space:]]*/, "**From:** ") }
if (/^To:/) { gsub(/^To:[[:space:]]*/, "**To:** ") }
if (/^Subject:/) { gsub(/^Subject:[[:space:]]*/, "**Subject:** ") }
if (/^Date:/) { gsub(/^Date:[[:space:]]*/, "**Date:** ") }
# Skip empty lines
if (/^[[:space:]]*$/) next
print
}

125
shell/email_processor.py Executable file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""
Email content processor for run_himalaya.sh
Cleans up email content for better readability
"""
import sys
import re
from urllib.parse import urlparse
def extract_domain(url):
"""Extract domain from URL"""
try:
parsed = urlparse(url if url.startswith("http") else "http://" + url)
return parsed.netloc.replace("www.", "")
except:
return None
def is_important_url(url, domain):
"""Determine if URL should be shown in full"""
important_domains = [
"github.com",
"gitlab.com",
"jira.",
"confluence.",
"docs.google.com",
"drive.google.com",
"sharepoint.com",
"slack.com",
"teams.microsoft.com",
"zoom.us",
]
# Show short URLs in full
if len(url) < 60:
return True
# Show URLs with important domains in shortened form
if domain and any(imp in domain for imp in important_domains):
return True
return False
def clean_url_defenses(text):
"""Remove URL defense wrappers"""
# Remove Proofpoint URL defense
text = re.sub(r"https://urldefense\.com/v3/__([^;]+)__;[^$]*\$", r"\1", text)
# Remove other common URL wrappers
text = re.sub(
r"https://[^/]*phishalarm[^/]*/[^/]*/[^$]*\$", "[Security Link Removed]", text
)
return text
def process_email_content(content):
"""Process email content for better readability"""
# Remove URL defense wrappers first
content = clean_url_defenses(content)
# Clean up common email artifacts first
content = re.sub(
r"ZjQcmQRYFpfpt.*?ZjQcmQRYFpfpt\w+End",
"[Security Banner Removed]",
content,
flags=re.DOTALL,
)
# Clean up mailto links that clutter the display
content = re.sub(r"mailto:[^\s>]+", "", content)
# Pattern to match URLs (more conservative)
url_pattern = r'https?://[^\s<>"{}|\\^`\[\]\(\)]+[^\s<>"{}|\\^`\[\]\(\).,;:!?]'
def replace_url(match):
url = match.group(0)
domain = extract_domain(url)
if is_important_url(url, domain):
if domain and len(url) > 60:
return f"[{domain}]"
else:
return url
else:
if domain:
return f"[{domain}]"
else:
return "[Link]"
# Replace URLs
content = re.sub(url_pattern, replace_url, content)
# Clean up email headers formatting
content = re.sub(
r"^(From|To|Subject|Date):\s*(.+?)$", r"**\1:** \2", content, flags=re.MULTILINE
)
# Clean up angle brackets around email addresses that are left over
content = re.sub(r"<[^>]*@[^>]*>", "", content)
# Remove excessive whitespace but preserve paragraph breaks
content = re.sub(r"\n\s*\n\s*\n+", "\n\n", content)
content = re.sub(r"[ \t]+", " ", content)
# Remove lines that are just whitespace
content = re.sub(r"^\s*$\n", "", content, flags=re.MULTILINE)
# Clean up repeated domain references on same line
content = re.sub(r"\[([^\]]+)\].*?\[\1\]", r"[\1]", content)
# Clean up trailing angle brackets and other artifacts
content = re.sub(r"[<>]+\s*$", "", content, flags=re.MULTILINE)
return content.strip()
if __name__ == "__main__":
content = sys.stdin.read()
processed = process_email_content(content)
print(processed)

262
shell/run_himalaya.sh Executable file
View File

@@ -0,0 +1,262 @@
#!/bin/bash
# Enhanced Himalaya Email Management Script
# Features: Auto-discovery, smart navigation, streamlined UX
# Function to get available message IDs from himalaya
get_available_message_ids() {
himalaya envelope list | awk 'NR > 2 && /^\| [0-9]/ {gsub(/[| ]/, "", $2); if($2 ~ /^[0-9]+$/) print $2}' | sort -n
}
# Function to get the latest (most recent) message ID
get_latest_message_id() {
get_available_message_ids | tail -1
}
# Function to find the next valid message ID
find_next_message_id() {
local current_id="$1"
get_available_message_ids | awk -v current="$current_id" '$1 > current {print $1; exit}'
}
# Function to find the previous valid message ID
find_previous_message_id() {
local current_id="$1"
get_available_message_ids | awk -v current="$current_id" '$1 < current {prev=$1} END {if(prev) print prev}'
}
# Function to refresh the vim-himalaya buffer
refresh_vim_himalaya() {
if [ -S "/tmp/nvim-server" ]; then
nvim --server /tmp/nvim-server --remote-send "Himalaya<CR>" 2>/dev/null
fi
}
# Function to read a single character without waiting for Enter
read_char() {
stty -echo -icanon time 0 min 1
char=$(dd bs=1 count=1 2>/dev/null)
stty echo icanon
echo "$char"
}
# Function to safely run the himalaya command and handle failures
run_himalaya_message_read() {
local message_id="$1"
local temp_output
temp_output=$(himalaya message read "$message_id" 2>&1)
local exit_code=$?
if [ $exit_code -eq 0 ]; then
# Choose email processor based on availability
local email_processor
if [ -f "email_processor.awk" ]; then
email_processor="awk -f email_processor.awk"
elif command -v python3 >/dev/null 2>&1 && [ -f "email_processor.py" ]; then
email_processor="python3 email_processor.py"
else
email_processor="cat" # fallback to no processing
fi
# Process email content, then render with glow and display with less
echo "$temp_output" | \
$email_processor | \
# bat
glow --width 100 -p
# less -R -X -F
return 0
else
echo "Failed to open message $message_id."
return 1
fi
}
# Function to create a task for the current message
create_task_for_message() {
local message_id="$1"
read -p "Task title: " task_title
if [ -n "$task_title" ]; then
if command -v task >/dev/null 2>&1; then
task add "Followup on email $message_id - $task_title" \
--project "Email" \
--due "$(date -d '+1 week' +%Y-%m-%d)" \
--priority "P3" \
--tags "email"
echo "Task created for message $message_id."
else
echo "TaskWarrior not found. Task not created."
fi
else
echo "No task title provided. Task not created."
fi
}
# Function to display the action menu and handle user choice
show_action_menu() {
local current_id="$1"
# Draw a nice border above the menu
echo ""
echo "════════════════════════════════════════════════════════════════════════════════"
echo " ACTIONS"
echo "════════════════════════════════════════════════════════════════════════════════"
echo ""
echo " t) Create task for this message"
echo " d) Delete message"
echo " a) Archive message"
echo " n) Next message"
echo " p) Previous message"
echo " r) Reopen/redisplay current message"
echo " q) Quit"
echo ""
echo "────────────────────────────────────────────────────────────────────────────────"
echo -n "Enter choice: "
local choice=$(read_char)
echo "$choice" # Echo for feedback
# Clear screen after choice is made
clear
case "$choice" in
t|T)
create_task_for_message "$current_id"
echo "Press any key to continue..."
read_char > /dev/null
process_message "$current_id"
;;
d|D)
echo "Deleting message $current_id..."
if himalaya message delete "$current_id"; then
echo "Message $current_id deleted successfully."
refresh_vim_himalaya
# Try to open next available message
local next_id=$(find_next_message_id "$current_id")
if [ -n "$next_id" ]; then
echo "Opening next message: $next_id"
process_message "$next_id"
else
echo "No more messages. Exiting."
exit 0
fi
else
echo "Failed to delete message $current_id."
echo "Press any key to continue..."
read_char > /dev/null
process_message "$current_id"
fi
;;
a|A)
echo "Archiving message $current_id..."
if himalaya message move Archives "$current_id"; then
echo "Message $current_id archived successfully."
refresh_vim_himalaya
# Try to open next available message
local next_id=$(find_next_message_id "$current_id")
if [ -n "$next_id" ]; then
echo "Opening next message: $next_id"
process_message "$next_id"
else
echo "No more messages. Exiting."
exit 0
fi
else
echo "Failed to archive message $current_id."
echo "Press any key to continue..."
read_char > /dev/null
process_message "$current_id"
fi
;;
n|N)
local next_id=$(find_next_message_id "$current_id")
if [ -n "$next_id" ]; then
echo "Opening next message: $next_id"
process_message "$next_id"
else
echo "No next message available."
echo "Press any key to continue..."
read_char > /dev/null
process_message "$current_id"
fi
;;
p|P)
local prev_id=$(find_previous_message_id "$current_id")
if [ -n "$prev_id" ]; then
echo "Opening previous message: $prev_id"
process_message "$prev_id"
else
echo "No previous message available."
echo "Press any key to continue..."
read_char > /dev/null
process_message "$current_id"
fi
;;
r|R)
echo "Reopening message $current_id..."
process_message "$current_id"
;;
q|Q)
echo "Exiting."
exit 0
;;
*)
echo "Invalid choice. Please try again."
echo "Press any key to continue..."
read_char > /dev/null
process_message "$current_id"
;;
esac
}
# Function to process a message (read and show menu)
process_message() {
local message_id="$1"
local is_fallback="$2"
echo "Reading message $message_id..."
if run_himalaya_message_read "$message_id"; then
show_action_menu "$message_id"
else
echo "Error reading message $message_id."
if [ "$is_fallback" = "true" ]; then
# If this was already a fallback attempt, don't try again
echo "Unable to read any messages. Exiting."
exit 1
else
# Try to find a valid message to fall back to
echo "Trying to find an available message..."
local latest_id=$(get_latest_message_id)
if [ -n "$latest_id" ] && [ "$latest_id" != "$message_id" ]; then
echo "Opening latest message: $latest_id"
process_message "$latest_id" "true"
else
echo "No valid messages found. Exiting."
exit 1
fi
fi
fi
}
# Main script logic
# Check if message ID is provided, otherwise get the latest
if [ -z "$1" ]; then
echo "No message ID provided. Finding latest message..."
message_id=$(get_latest_message_id)
if [ -z "$message_id" ]; then
echo "No messages found in inbox."
exit 1
fi
echo "Latest message ID: $message_id"
else
message_id="$1"
fi
# Validate that himalaya is available
if ! command -v himalaya >/dev/null 2>&1; then
echo "Error: himalaya command not found. Please install himalaya."
exit 1
fi
# Start processing the message
process_message "$message_id"

41
shell/run_tests.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Test runner for run_himalaya.sh
echo "===================="
echo "Himalaya Script Test Suite"
echo "===================="
echo
# Check if glow is available (needed for message display)
if ! command -v glow >/dev/null 2>&1; then
echo "Warning: glow not found. Some tests may behave differently."
echo "Install with: brew install glow"
echo
fi
# Get the script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Set up PATH to include our test mocks
export PATH="$SCRIPT_DIR/tests:$PATH"
# Run unit tests
echo "=== Unit Tests ==="
if ! bash "$SCRIPT_DIR/tests/unit_tests.sh"; then
echo "Unit tests failed!"
exit 1
fi
echo
echo "=== Integration Tests ==="
if ! bash "$SCRIPT_DIR/tests/integration_tests.sh"; then
echo "Integration tests failed!"
exit 1
fi
echo
echo "===================="
echo "All tests passed! ✅"
echo "===================="

8
shell/test_refactored.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Test script for the refactored code
echo "Testing the refactored code with a dry run (no attachment download)..."
python3 main.py sync --dry-run
echo -e "\nTesting with attachment downloading enabled..."
python3 main.py sync --dry-run --download-attachments