mirror of
https://github.com/hanzoskill/morning-email-rollup.git
synced 2026-10-30 21:14:08 +00:00
207 lines
7.1 KiB
Bash
207 lines
7.1 KiB
Bash
#!/bin/bash
|
|
# Morning Email Rollup - Important emails from the last 24 hours
|
|
|
|
# Temporarily disable strict mode for debugging
|
|
# set -uo pipefail
|
|
|
|
GOG_ACCOUNT="${GOG_ACCOUNT:-your[email protected]}"
|
|
MAX_EMAILS="${MAX_EMAILS:-10}" # Default to 10, can be overridden via environment variable
|
|
WORKSPACE="${BOT_WORKSPACE:-$HOME/bot}"
|
|
LOG_FILE="${WORKSPACE}/morning-email-rollup-log.md"
|
|
|
|
# Initialize log
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
|
|
# Log with timestamp
|
|
log() {
|
|
echo "- [$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
|
}
|
|
|
|
echo "📧 **Morning Email Rollup** - $(date '+%A, %B %d, %Y')"
|
|
echo ""
|
|
|
|
log "🔄 Starting morning email rollup"
|
|
|
|
# Check for calendar events (gog) - graceful fallback if not installed
|
|
if command -v gog &> /dev/null; then
|
|
TODAY=$(date '+%Y-%m-%d')
|
|
TOMORROW=$(date -d "$TODAY + 1 day" '+%Y-%m-%d')
|
|
CALENDAR_EVENTS=$(gog calendar events primary --from "$TODAY" --to "$TOMORROW" --account "$GOG_ACCOUNT" 2>/dev/null)
|
|
|
|
# Check if there are events (more than just the header line)
|
|
EVENT_COUNT=$(echo "$CALENDAR_EVENTS" | tail -n +2 | grep -c . 2>/dev/null || echo "0")
|
|
# Ensure EVENT_COUNT is a clean number
|
|
EVENT_COUNT=$(echo "$EVENT_COUNT" | tr -cd '0-9')
|
|
|
|
if [[ "$EVENT_COUNT" -gt 0 ]]; then
|
|
echo "📅 **$EVENT_COUNT calendar event(s) today**"
|
|
|
|
# Parse and format events (skip header line)
|
|
echo "$CALENDAR_EVENTS" | tail -n +2 | while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
|
|
# Parse gog output: ID START END SUMMARY
|
|
start_full=$(echo "$line" | awk '{print $2}')
|
|
end_full=$(echo "$line" | awk '{print $3}')
|
|
title=$(echo "$line" | awk '{$1=$2=$3=""; print $0}' | sed 's/^[[:space:]]*//')
|
|
|
|
# Extract time from ISO format (e.g., 2026-01-15T05:00:00-07:00)
|
|
start_time=$(echo "$start_full" | cut -d'T' -f2 | cut -d'-' -f1 | cut -d'+' -f1 | cut -d':' -f1-2)
|
|
end_time=$(echo "$end_full" | cut -d'T' -f2 | cut -d'-' -f1 | cut -d'+' -f1 | cut -d':' -f1-2)
|
|
|
|
# Convert to 12-hour format
|
|
start_12h=$(date -d "$start_time" '+%I:%M %p' 2>/dev/null | sed 's/^0//' | sed 's/:00//')
|
|
end_12h=$(date -d "$end_time" '+%I:%M %p' 2>/dev/null | sed 's/^0//' | sed 's/:00//')
|
|
|
|
echo "• $title - $start_12h to $end_12h"
|
|
done
|
|
echo ""
|
|
log "📅 Calendar events listed ($EVENT_COUNT events)"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Search for important/starred emails from last 24 hours
|
|
IMPORTANT_EMAILS=$(gog gmail search 'is:important OR is:starred newer_than:1d' --max 20 --account "$GOG_ACCOUNT" --json 2>/dev/null)
|
|
|
|
if [[ -z "$IMPORTANT_EMAILS" ]] || [[ "$IMPORTANT_EMAILS" == "null" ]]; then
|
|
echo "✅ No important emails in the last 24 hours."
|
|
log "✅ No important emails found"
|
|
exit 0
|
|
fi
|
|
|
|
# Count emails
|
|
EMAIL_COUNT=$(echo "$IMPORTANT_EMAILS" | jq -r '.threads | length' 2>/dev/null || echo "0")
|
|
|
|
if [[ "$EMAIL_COUNT" -eq 0 ]]; then
|
|
echo "✅ No important emails in the last 24 hours."
|
|
log "✅ No important emails found"
|
|
exit 0
|
|
fi
|
|
|
|
echo "📧 **$EMAIL_COUNT important email(s) from last 24 hours**"
|
|
if [[ $EMAIL_COUNT -gt $MAX_EMAILS ]]; then
|
|
echo "(Showing top $MAX_EMAILS)"
|
|
fi
|
|
echo ""
|
|
|
|
# Function to summarize email using gemini
|
|
summarize_email() {
|
|
local body="$1"
|
|
|
|
# Check if gemini is available
|
|
if ! command -v gemini &> /dev/null; then
|
|
# Fallback: clean and return first readable sentence if gemini not available
|
|
echo "$body" | sed 's/<[^>]*>//g' | sed 's/&[a-z]*;//g' | tr -s ' ' | sed 's/^ *//' | sed 's/\./.\n/g' | head -1 | head -c 150
|
|
return
|
|
fi
|
|
|
|
# Use gemini to get a 1-sentence summary
|
|
# Pass body as part of the prompt (piping doesn't work with prompts)
|
|
# IMPORTANT: Redirect stdin to /dev/null to prevent consuming the while loop's input
|
|
local summary
|
|
summary=$(gemini --model gemini-2.0-flash "Summarize this email in exactly 1 sentence of natural language. Make it medium to long length. Don't use quotes:
|
|
|
|
$body" </dev/null 2>&1 | grep -v "Loaded cached" | tail -1)
|
|
|
|
# Clean up the summary (remove any quotes, newlines, extra spaces)
|
|
summary=$(echo "$summary" | sed 's/^["'\'']//' | sed 's/["'\'']$//' | tr -s ' ' | sed 's/^ *//' | sed 's/ *$//' | head -1)
|
|
|
|
# If gemini failed or returned empty, return cleaned body snippet
|
|
if [[ -z "$summary" ]] || [[ ${#summary} -lt 10 ]]; then
|
|
echo "$body" | sed 's/<[^>]*>//g' | sed 's/&[a-z]*;//g' | tr -s ' ' | sed 's/^ *//' | head -c 200
|
|
else
|
|
echo "$summary"
|
|
fi
|
|
}
|
|
|
|
# Process up to MAX_EMAILS
|
|
email_counter=0
|
|
# Use a temporary file for the thread IDs to avoid subshell issues
|
|
THREAD_IDS=$(echo "$IMPORTANT_EMAILS" | jq -r '.threads[] | "\(.id)"')
|
|
# Use a temp file to avoid <<< issues
|
|
TEMP_THREAD_IDS=$(mktemp)
|
|
echo "$THREAD_IDS" > "$TEMP_THREAD_IDS"
|
|
while IFS= read -r thread_id; do
|
|
[[ -z "$thread_id" ]] && continue
|
|
|
|
# Limit to MAX_EMAILS
|
|
if [[ $email_counter -ge $MAX_EMAILS ]]; then
|
|
break
|
|
fi
|
|
email_counter=$((email_counter + 1))
|
|
|
|
# Skip thread_id if it's not an actual ID (jq output validation)
|
|
if [[ "$thread_id" =~ ^[a-f0-9]{12,}$ ]]; then
|
|
# Continue processing
|
|
:
|
|
else
|
|
continue
|
|
fi
|
|
|
|
# Get email details
|
|
email_data=$(gog gmail get "$thread_id" --account "$GOG_ACCOUNT" 2>/dev/null) || true
|
|
|
|
if [[ -z "$email_data" ]]; then
|
|
echo "DEBUG: No email data for thread_id=$thread_id" >&2
|
|
continue
|
|
fi
|
|
|
|
# Extract fields
|
|
from=$(echo "$email_data" | grep "^from" | cut -f2-)
|
|
subject=$(echo "$email_data" | grep "^subject" | cut -f2-)
|
|
|
|
# Clean subject - remove extra quotes and trim whitespace
|
|
subject=$(echo "$subject" | sed 's/^["'\'']//' | sed 's/["'\'']$//' | sed 's/^ *//' | sed 's/ *$//')
|
|
|
|
# Get sender name only (strip email address)
|
|
sender_name=$(echo "$from" | sed 's/<.*>//' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
if [[ -z "$sender_name" ]]; then
|
|
sender_name="$from"
|
|
fi
|
|
|
|
# Get email body (after headers)
|
|
body=$(echo "$email_data" | awk 'BEGIN{body=0} /^$/{body=1; next} body{print}')
|
|
|
|
# Clean body - remove HTML/CSS and convert to plain text
|
|
cleaned_body=$(echo "$body" | \
|
|
sed 's/<[^>]*>//g' | \
|
|
sed 's/ / /g' | \
|
|
sed 's/&/\&/g' | \
|
|
sed 's/</</g' | \
|
|
sed 's/>/>/g' | \
|
|
sed 's/@media[^}]*}//g' | \
|
|
sed 's/{[^}]*}//g' | \
|
|
sed 's/\[[^]]*\]//g' | \
|
|
tr '\n' ' ' | \
|
|
sed 's/\\n/ /g' | \
|
|
sed 's/ */ /g' | \
|
|
sed 's/^ *//' | \
|
|
head -c 5000)
|
|
|
|
# Get AI summary
|
|
summary=$(summarize_email "$cleaned_body") || true
|
|
|
|
# Check read/unread status
|
|
labels=$(echo "$email_data" | grep "^label_ids" | cut -f2-)
|
|
if [[ "$labels" == *"UNREAD"* ]]; then
|
|
read_marker="🔴 "
|
|
else
|
|
read_marker="🟢 "
|
|
fi
|
|
|
|
echo "${read_marker}**${sender_name}: ${subject}**"
|
|
echo " $summary"
|
|
echo ""
|
|
|
|
done < "$TEMP_THREAD_IDS"
|
|
|
|
# Clean up temp file
|
|
rm -f "$TEMP_THREAD_IDS"
|
|
|
|
log "✅ Rollup complete: $EMAIL_COUNT emails"
|
|
echo ""
|
|
echo "---"
|
|
echo "💡 **Need more details?** Ask me to read or search specific emails."
|